Avatar editor updates
7
src/api/nitro/avatar/GetAvatarPalette.ts
Normal 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);
|
||||
}
|
7
src/api/nitro/avatar/GetAvatarSetType.ts
Normal 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);
|
||||
}
|
@ -1 +1,3 @@
|
||||
export * from './GetAvatarPalette';
|
||||
export * from './GetAvatarRenderManager';
|
||||
export * from './GetAvatarSetType';
|
||||
|
6
src/api/nitro/session/GetClubMemberLevel.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { GetSessionDataManager } from './GetSessionDataManager';
|
||||
|
||||
export function GetClubMemberLevel(): number
|
||||
{
|
||||
return GetSessionDataManager().clubLevel;
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
export * from './CanManipulateFurniture';
|
||||
export * from './GetCanStandUp';
|
||||
export * from './GetCanUseExpression';
|
||||
export * from './GetClubMemberLevel';
|
||||
export * from './GetFurnitureDataForProductOffer';
|
||||
export * from './GetFurnitureDataForRoomObject';
|
||||
export * from './GetOwnPosture';
|
||||
|
Before Width: | Height: | Size: 263 B After Width: | Height: | Size: 272 B |
Before Width: | Height: | Size: 146 B After Width: | Height: | Size: 236 B |
Before Width: | Height: | Size: 196 B After Width: | Height: | Size: 270 B |
Before Width: | Height: | Size: 135 B After Width: | Height: | Size: 196 B |
Before Width: | Height: | Size: 162 B After Width: | Height: | Size: 236 B |
Before Width: | Height: | Size: 137 B After Width: | Height: | Size: 228 B |
Before Width: | Height: | Size: 195 B After Width: | Height: | Size: 256 B |
Before Width: | Height: | Size: 122 B After Width: | Height: | Size: 208 B |
Before Width: | Height: | Size: 165 B After Width: | Height: | Size: 266 B |
Before Width: | Height: | Size: 164 B After Width: | Height: | Size: 257 B |
Before Width: | Height: | Size: 199 B After Width: | Height: | Size: 308 B |
@ -179,15 +179,14 @@
|
||||
|
||||
&.clear-icon {
|
||||
background-image: url('../images/avatareditor/clear-icon.png');
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
width: 27px;
|
||||
height: 27px;
|
||||
}
|
||||
|
||||
&.ca-icon {
|
||||
background-image: url('../images/avatareditor/ca-icon.png');
|
||||
width: 30px;
|
||||
height: 24px;
|
||||
background
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
|
||||
&.selected {
|
||||
background-image: url('../images/avatareditor/ca-selected-icon.png');
|
||||
@ -216,8 +215,8 @@
|
||||
|
||||
&.cp-icon {
|
||||
background-image: url('../images/avatareditor/cp-icon.png');
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
width: 30px;
|
||||
height: 24px;
|
||||
|
||||
&.selected {
|
||||
background-image: url('../images/avatareditor/cp-selected-icon.png');
|
||||
|
@ -1,6 +1,8 @@
|
||||
.content-area {
|
||||
padding-top: $container-padding-x;
|
||||
padding-bottom: $container-padding-x;
|
||||
resize: vertical;
|
||||
overflow: auto;
|
||||
|
||||
&.simple {
|
||||
padding-left: ($container-padding-x + 25px);
|
||||
|
@ -4,11 +4,11 @@ import { NitroCardGridThemes, NitroCardGridViewProps } from './NitroCardGridView
|
||||
|
||||
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 (
|
||||
<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` }>
|
||||
{ children }
|
||||
</div>
|
||||
|
@ -1,4 +1,6 @@
|
||||
export interface NitroCardGridViewProps
|
||||
import { DetailsHTMLAttributes } from 'react';
|
||||
|
||||
export interface NitroCardGridViewProps extends DetailsHTMLAttributes<HTMLDivElement>
|
||||
{
|
||||
columns?: number;
|
||||
theme?: string;
|
||||
|
@ -3,6 +3,9 @@
|
||||
max-height: 50px;
|
||||
|
||||
.grid-item {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
@ -1,9 +1,13 @@
|
||||
.nitro-avatar-editor {
|
||||
width: 600px;
|
||||
width: 620px;
|
||||
|
||||
.content-area {
|
||||
height: 330px;
|
||||
max-height: 330px;
|
||||
min-height: 300px;
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
.category-item {
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.figure-preview-container {
|
||||
@ -13,27 +17,53 @@
|
||||
overflow: hidden;
|
||||
z-index: 1;
|
||||
|
||||
.avatar-image {
|
||||
margin: 45px auto 0;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.arrow-container {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
padding: 0 10px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
justify-content: space-between;
|
||||
bottom: 12px;
|
||||
z-index: 3;
|
||||
z-index: 5;
|
||||
|
||||
.icon {
|
||||
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 {
|
||||
@ -44,7 +74,8 @@
|
||||
left: 0;
|
||||
right: 0;
|
||||
border-radius: 50%;
|
||||
background-color: red;
|
||||
background-color: $pale-sky;
|
||||
box-shadow: 0 0 8px 2px rgba($white,.6);
|
||||
transform: scale(2);
|
||||
}
|
||||
}
|
||||
|
@ -1,103 +1,91 @@
|
||||
import { AvatarEditorFigureCategory } from 'nitro-renderer';
|
||||
import { FC, useCallback, useEffect, useReducer, useState } from 'react';
|
||||
import { AvatarDirectionAngle, AvatarEditorFigureCategory, UserFigureComposer } from 'nitro-renderer';
|
||||
import { FC, useCallback, useEffect, useState } from 'react';
|
||||
import { GetSessionDataManager } from '../../api';
|
||||
import { AvatarEditorEvent } from '../../events/avatar-editor';
|
||||
import { SendMessageHook } from '../../hooks';
|
||||
import { useUiEvent } from '../../hooks/events/ui/ui-event';
|
||||
import { NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView } from '../../layout';
|
||||
import { LocalizeText } from '../../utils/LocalizeText';
|
||||
import { AvatarEditorViewProps } from './AvatarEditorView.types';
|
||||
import { AvatarEditor } from './common/AvatarEditor';
|
||||
import { AvatarEditorUtilities } from './common/AvatarEditorUtilities';
|
||||
import { BodyModel } from './common/BodyModel';
|
||||
import { FigureData } from './common/FigureData';
|
||||
import { HeadModel } from './common/HeadModel';
|
||||
import { IAvatarEditorCategoryModel } from './common/IAvatarEditorCategoryModel';
|
||||
import { LegModel } from './common/LegModel';
|
||||
import { TorsoModel } from './common/TorsoModel';
|
||||
import { AvatarEditorContextProvider } from './context/AvatarEditorContext';
|
||||
import { AvatarEditorReducer, initialAvatarEditor } from './reducers/AvatarEditorReducer';
|
||||
import { AvatarEditorFigurePreviewView } from './views/figure-preview/AvatarEditorFigurePreviewView';
|
||||
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 =>
|
||||
{
|
||||
const [ isVisible, setIsVisible ] = useState(false);
|
||||
const [ avatarEditorState, dispatchAvatarEditorState ] = useReducer(AvatarEditorReducer, initialAvatarEditor);
|
||||
const [ avatarEditor, setAvatarEditor ] = useState<AvatarEditor>(null);
|
||||
const [ figures, setFigures ] = useState<Map<string, FigureData>>(null);
|
||||
const [ figureData, setFigureData ] = useState<FigureData>(null);
|
||||
const [ categories, setCategories ] = useState<Map<string, 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 selectCategory = useCallback((name: string) =>
|
||||
{
|
||||
if(!categories) return;
|
||||
|
||||
setActiveCategory(categories.get(name));
|
||||
}, [ categories ]);
|
||||
|
||||
const resetCategories = useCallback((editor: AvatarEditor) =>
|
||||
const resetCategories = useCallback(() =>
|
||||
{
|
||||
const categories = new Map();
|
||||
|
||||
categories.set(AvatarEditorFigureCategory.GENERIC, new BodyModel(editor));
|
||||
categories.set(AvatarEditorFigureCategory.HEAD, new HeadModel(editor));
|
||||
categories.set(AvatarEditorFigureCategory.TORSO, new TorsoModel(editor));
|
||||
categories.set(AvatarEditorFigureCategory.LEGS, new LegModel(editor));
|
||||
categories.set(AvatarEditorFigureCategory.GENERIC, new BodyModel());
|
||||
categories.set(AvatarEditorFigureCategory.HEAD, new HeadModel());
|
||||
categories.set(AvatarEditorFigureCategory.TORSO, new TorsoModel());
|
||||
categories.set(AvatarEditorFigureCategory.LEGS, new LegModel());
|
||||
|
||||
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);
|
||||
}, [ avatarEditor, resetCategories ]);
|
||||
maleFigure.loadAvatarData(DEFAULT_MALE_FIGURE, FigureData.MALE);
|
||||
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) =>
|
||||
{
|
||||
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:
|
||||
case 'm':
|
||||
case 'M':
|
||||
gender = FigureData.MALE;
|
||||
break;
|
||||
case FigureData.FEMALE:
|
||||
case 'f':
|
||||
case 'F':
|
||||
gender = FigureData.FEMALE;
|
||||
break;
|
||||
default:
|
||||
gender = FigureData.MALE;
|
||||
setLastFigure(figureData.getFigureString());
|
||||
setLastGender(figureData.gender);
|
||||
}
|
||||
|
||||
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 ]);
|
||||
}, [ figures, figureData ]);
|
||||
|
||||
const onAvatarEditorEvent = useCallback((event: AvatarEditorEvent) =>
|
||||
{
|
||||
@ -105,12 +93,20 @@ export const AvatarEditorView: FC<AvatarEditorViewProps> = props =>
|
||||
{
|
||||
case AvatarEditorEvent.SHOW_EDITOR:
|
||||
setIsVisible(true);
|
||||
setNeedsReset(true);
|
||||
return;
|
||||
case AvatarEditorEvent.HIDE_EDITOR:
|
||||
setIsVisible(false);
|
||||
return;
|
||||
case AvatarEditorEvent.TOGGLE_EDITOR:
|
||||
setIsVisible(prevValue => !prevValue);
|
||||
setIsVisible(prevValue =>
|
||||
{
|
||||
const flag = !prevValue;
|
||||
|
||||
if(flag) setNeedsReset(true);
|
||||
|
||||
return flag;
|
||||
});
|
||||
return;
|
||||
}
|
||||
}, []);
|
||||
@ -119,42 +115,130 @@ export const AvatarEditorView: FC<AvatarEditorViewProps> = props =>
|
||||
useUiEvent(AvatarEditorEvent.HIDE_EDITOR, onAvatarEditorEvent);
|
||||
useUiEvent(AvatarEditorEvent.TOGGLE_EDITOR, onAvatarEditorEvent);
|
||||
|
||||
useEffect(() =>
|
||||
const clearFigure = useCallback(() =>
|
||||
{
|
||||
if(!isVisible || isInitalized) return;
|
||||
loadAvatarInEditor(figureData.getFigureStringWithFace(0, false), figureData.gender, false);
|
||||
resetCategories();
|
||||
}, [ figureData, loadAvatarInEditor, resetCategories ]);
|
||||
|
||||
const newEditor = new AvatarEditor();
|
||||
const resetFigure = useCallback(() =>
|
||||
{
|
||||
loadAvatarInEditor(lastFigure, lastGender);
|
||||
resetCategories();
|
||||
}, [ lastFigure, lastGender, loadAvatarInEditor, resetCategories ]);
|
||||
|
||||
setAvatarEditor(newEditor);
|
||||
setIsInitalized(true);
|
||||
}, [ isVisible, isInitalized ]);
|
||||
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(() =>
|
||||
{
|
||||
if(!isVisible || !avatarEditor) return;
|
||||
if(!categories) return;
|
||||
|
||||
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();
|
||||
|
||||
setIsInitalized(true);
|
||||
|
||||
return;
|
||||
}
|
||||
}, [ isVisible, figures, setupFigures ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!isVisible || !isInitalized || !needsReset) return;
|
||||
|
||||
loadAvatarInEditor(GetSessionDataManager().figure, GetSessionDataManager().gender);
|
||||
}, [ isVisible, avatarEditor, loadAvatarInEditor ]);
|
||||
setNeedsReset(false);
|
||||
}, [ isVisible, isInitalized, needsReset, loadAvatarInEditor ]);
|
||||
|
||||
if(!isVisible) return null;
|
||||
|
||||
return (
|
||||
<AvatarEditorContextProvider value={ { avatarEditorState, dispatchAvatarEditorState } }>
|
||||
{ isVisible &&
|
||||
<NitroCardView className="nitro-avatar-editor">
|
||||
<NitroCardHeaderView headerText={ LocalizeText('avatareditor.title') } onCloseClick={ event => setIsVisible(false) } />
|
||||
<NitroCardTabsView>
|
||||
{ categories && (categories.size > 0) && Array.from(categories.keys()).map(category =>
|
||||
{
|
||||
return (
|
||||
<NitroCardTabsItemView key={ category } isActive={ (activeCategory.name === category) } onClick={ event => selectCategory(category) }>
|
||||
{ LocalizeText(`avatareditor.category.${ category }`) }
|
||||
</NitroCardTabsItemView>
|
||||
);
|
||||
})}
|
||||
</NitroCardTabsView>
|
||||
<NitroCardContentView>
|
||||
{ activeCategory && <AvatarEditorModelView model={ activeCategory } editor={ avatarEditor } selectGender={ selectGender } /> }
|
||||
</NitroCardContentView>
|
||||
</NitroCardView> }
|
||||
</AvatarEditorContextProvider>
|
||||
<NitroCardView className="nitro-avatar-editor">
|
||||
<NitroCardHeaderView headerText={ LocalizeText('avatareditor.title') } onCloseClick={ event => setIsVisible(false) } />
|
||||
<NitroCardTabsView>
|
||||
{ categories && (categories.size > 0) && activeCategory && Array.from(categories.keys()).map(category =>
|
||||
{
|
||||
return (
|
||||
<NitroCardTabsItemView key={ category } isActive={ (activeCategory.name === category) } onClick={ event => selectCategory(category) }>
|
||||
{ LocalizeText(`avatareditor.category.${ category }`) }
|
||||
</NitroCardTabsItemView>
|
||||
);
|
||||
})}
|
||||
</NitroCardTabsView>
|
||||
<NitroCardContentView>
|
||||
<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>
|
||||
</NitroCardView>
|
||||
);
|
||||
}
|
||||
|
@ -1,88 +1,79 @@
|
||||
import { IPalette, IPartColor, ISetType, IStructureData } from 'nitro-renderer';
|
||||
import { GetAvatarRenderManager, GetConfiguration, GetSessionDataManager } from '../../../api';
|
||||
import { IPartColor } from 'nitro-renderer';
|
||||
import { GetAvatarPalette, GetAvatarRenderManager, GetAvatarSetType, GetClubMemberLevel, GetConfiguration } from '../../../api';
|
||||
import { AvatarEditorGridColorItem } from './AvatarEditorGridColorItem';
|
||||
import { AvatarEditorGridPartItem } from './AvatarEditorGridPartItem';
|
||||
import { CategoryBaseModel } from './CategoryBaseModel';
|
||||
import { CategoryData } from './CategoryData';
|
||||
import { FigureData } from './FigureData';
|
||||
|
||||
const MAX_PALETTES: number = 2;
|
||||
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
|
||||
export class AvatarEditorUtilities
|
||||
{
|
||||
private _figureStructureData: IStructureData = GetAvatarRenderManager().structureData;
|
||||
private _figures: Map<string, FigureData> = new Map();
|
||||
private _gender: string = FigureData.MALE;
|
||||
private _notifier: () => void = null;
|
||||
private static MAX_PALETTES: number = 2;
|
||||
|
||||
constructor()
|
||||
public static CURRENT_FIGURE: FigureData = null;
|
||||
|
||||
public static getGender(gender: string): string
|
||||
{
|
||||
const maleFigure = new FigureData();
|
||||
const femaleFigure = new FigureData();
|
||||
switch(gender)
|
||||
{
|
||||
case FigureData.MALE:
|
||||
case 'm':
|
||||
case 'M':
|
||||
gender = FigureData.MALE;
|
||||
break;
|
||||
case FigureData.FEMALE:
|
||||
case 'f':
|
||||
case 'F':
|
||||
gender = FigureData.FEMALE;
|
||||
break;
|
||||
default:
|
||||
gender = FigureData.MALE;
|
||||
}
|
||||
|
||||
maleFigure.loadAvatarData(DEFAULT_MALE_FIGURE, FigureData.MALE);
|
||||
femaleFigure.loadAvatarData(DEFAULT_FEMALE_FIGURE, FigureData.FEMALE);
|
||||
|
||||
this._figures.set(FigureData.MALE, maleFigure);
|
||||
this._figures.set(FigureData.FEMALE, femaleFigure);
|
||||
return gender;
|
||||
}
|
||||
|
||||
public getSetType(setType: string): ISetType
|
||||
public static createCategory(model: CategoryBaseModel, name: string): CategoryData
|
||||
{
|
||||
if(!this._figureStructureData) return null;
|
||||
|
||||
return this._figureStructureData.getSetType(setType);
|
||||
}
|
||||
|
||||
public getPalette(paletteId: number): IPalette
|
||||
{
|
||||
if(!this._figureStructureData) return null;
|
||||
|
||||
return this._figureStructureData.getPalette(paletteId);
|
||||
}
|
||||
|
||||
public createCategory(model: CategoryBaseModel, name: string): CategoryData
|
||||
{
|
||||
if(!model || !name) return null;
|
||||
if(!model || !name || !this.CURRENT_FIGURE) return null;
|
||||
|
||||
const partItems: AvatarEditorGridPartItem[] = [];
|
||||
const colorItems: AvatarEditorGridColorItem[][] = [];
|
||||
|
||||
let i = 0;
|
||||
|
||||
while(i < MAX_PALETTES)
|
||||
while(i < this.MAX_PALETTES)
|
||||
{
|
||||
colorItems.push([]);
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
const setType = this.getSetType(name);
|
||||
const setType = GetAvatarSetType(name);
|
||||
|
||||
if(!setType) return null;
|
||||
|
||||
const palette = this.getPalette(setType.paletteID);
|
||||
const palette = GetAvatarPalette(setType.paletteID);
|
||||
|
||||
if(!palette) return null;
|
||||
|
||||
let colorIds = this.figureData.getColourIds(name);
|
||||
let colorIds = this.CURRENT_FIGURE.getColorIds(name);
|
||||
|
||||
if(!colorIds) colorIds = [];
|
||||
|
||||
const partColors: IPartColor[] = new Array(colorIds.length);
|
||||
const clubItemsDimmed = this.clubItemsDimmed;
|
||||
const clubMemberLevel = GetClubMemberLevel();
|
||||
|
||||
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;
|
||||
|
||||
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);
|
||||
|
||||
colorItems[i].push(colorItem);
|
||||
@ -108,11 +99,11 @@ export class AvatarEditor
|
||||
|
||||
if(clubItemsDimmed)
|
||||
{
|
||||
mandatorySetIds = GetAvatarRenderManager().getMandatoryAvatarPartSetIds(this._gender, 2);
|
||||
mandatorySetIds = GetAvatarRenderManager().getMandatoryAvatarPartSetIds(this.CURRENT_FIGURE.gender, 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
mandatorySetIds = GetAvatarRenderManager().getMandatoryAvatarPartSetIds(this._gender, this.clubMemberLevel);
|
||||
mandatorySetIds = GetAvatarRenderManager().getMandatoryAvatarPartSetIds(this.CURRENT_FIGURE.gender, clubMemberLevel);
|
||||
}
|
||||
|
||||
const isntMandatorySet = (mandatorySetIds.indexOf(name) === -1);
|
||||
@ -142,17 +133,15 @@ export class AvatarEditor
|
||||
{
|
||||
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;
|
||||
|
||||
@ -186,7 +175,7 @@ export class AvatarEditor
|
||||
|
||||
i = 0;
|
||||
|
||||
while(i < MAX_PALETTES)
|
||||
while(i < this.MAX_PALETTES)
|
||||
{
|
||||
colorItems[i].sort(this.colorSorter);
|
||||
|
||||
@ -196,7 +185,7 @@ export class AvatarEditor
|
||||
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 clubLevelB = (!b.partSet ? 9999999999 : b.partSet.clubLevel);
|
||||
@ -218,7 +207,23 @@ export class AvatarEditor
|
||||
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 clubLevelB = (!b.partSet ? -1 : b.partSet.clubLevel);
|
||||
@ -240,67 +245,33 @@ export class AvatarEditor
|
||||
return 0;
|
||||
}
|
||||
|
||||
private colorSorter(a: AvatarEditorGridColorItem, b: AvatarEditorGridColorItem): number
|
||||
public static avatarSetFirstSelectableColor(name: string): number
|
||||
{
|
||||
const clubLevelA = (!a.partColor ? -1 : a.partColor.clubLevel);
|
||||
const clubLevelB = (!b.partColor ? -1 : b.partColor.clubLevel);
|
||||
const setType = GetAvatarSetType(name);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public get clubMemberLevel(): number
|
||||
{
|
||||
return GetSessionDataManager().clubLevel;
|
||||
}
|
||||
|
||||
private get clubItemsFirst(): boolean
|
||||
public static get clubItemsFirst(): boolean
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
import { AvatarEditorFigureCategory, AvatarScaleType, AvatarSetType, IAvatarImageListener } from 'nitro-renderer';
|
||||
import { GetAvatarRenderManager } from '../../../api';
|
||||
import { AvatarEditorUtilities } from './AvatarEditorUtilities';
|
||||
import { CategoryBaseModel } from './CategoryBaseModel';
|
||||
import { FigureData } from './FigureData';
|
||||
|
||||
@ -25,15 +26,15 @@ export class BodyModel extends CategoryBaseModel implements IAvatarImageListener
|
||||
|
||||
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);
|
||||
|
||||
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 = [];
|
||||
|
||||
@ -42,7 +43,7 @@ export class BodyModel extends CategoryBaseModel implements IAvatarImageListener
|
||||
|
||||
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 sprite = avatarImage.getImageAsSprite(AvatarSetType.HEAD);
|
||||
|
@ -1,18 +1,16 @@
|
||||
import { AvatarEditor } from './AvatarEditor';
|
||||
import { AvatarEditorUtilities } from './AvatarEditorUtilities';
|
||||
import { CategoryData } from './CategoryData';
|
||||
import { IAvatarEditorCategoryModel } from './IAvatarEditorCategoryModel';
|
||||
|
||||
export class CategoryBaseModel implements IAvatarEditorCategoryModel
|
||||
{
|
||||
protected _editor: AvatarEditor;
|
||||
protected _categories: Map<string, CategoryData>;
|
||||
protected _isInitalized: boolean;
|
||||
protected _maxPaletteCount: number;
|
||||
private _disposed: boolean;
|
||||
|
||||
constructor(editor: AvatarEditor)
|
||||
constructor()
|
||||
{
|
||||
this._editor = editor;
|
||||
this._isInitalized = false;
|
||||
this._maxPaletteCount = 0;
|
||||
}
|
||||
@ -51,7 +49,7 @@ export class CategoryBaseModel implements IAvatarEditorCategoryModel
|
||||
|
||||
if(existing) return;
|
||||
|
||||
existing = this._editor.createCategory(this, name);
|
||||
existing = AvatarEditorUtilities.createCategory(this, name);
|
||||
|
||||
if(!existing) return;
|
||||
|
||||
@ -66,9 +64,9 @@ export class CategoryBaseModel implements IAvatarEditorCategoryModel
|
||||
|
||||
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 = [];
|
||||
|
||||
@ -120,9 +118,9 @@ export class CategoryBaseModel implements IAvatarEditorCategoryModel
|
||||
{
|
||||
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;
|
||||
@ -148,9 +146,9 @@ export class CategoryBaseModel implements IAvatarEditorCategoryModel
|
||||
{
|
||||
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;
|
||||
@ -185,7 +183,7 @@ export class CategoryBaseModel implements IAvatarEditorCategoryModel
|
||||
|
||||
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
|
||||
@ -209,7 +207,7 @@ export class CategoryBaseModel implements IAvatarEditorCategoryModel
|
||||
return;
|
||||
}
|
||||
|
||||
this._editor.figureData.savePartSetColourId(category, categoryData.getSelectedColorIds(), true);
|
||||
AvatarEditorUtilities.CURRENT_FIGURE.savePartSetColourId(category, categoryData.getSelectedColorIds(), true);
|
||||
}
|
||||
|
||||
public getCategoryData(category: string): CategoryData
|
||||
|
@ -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 FEMALE: string = 'F';
|
||||
public static UNISEX: string = 'U';
|
||||
@ -24,6 +28,7 @@
|
||||
private _data: Map<string, number>;
|
||||
private _colors: Map<string, number[]>;
|
||||
private _gender: string = 'M';
|
||||
private _direction: number = FigureData.DEFAULT_DIRECTION;
|
||||
private _avatarEffectType: number = -1;
|
||||
private _notifier: () => void = null;
|
||||
|
||||
@ -80,14 +85,13 @@
|
||||
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;
|
||||
|
||||
return [];
|
||||
// return [this._avatarEditor._Str_24919(k)];
|
||||
return [ AvatarEditorUtilities.avatarSetFirstSelectableColor(setType) ];
|
||||
}
|
||||
|
||||
public getFigureString(): string
|
||||
@ -167,9 +171,9 @@
|
||||
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.HAIR:
|
||||
@ -184,11 +188,11 @@
|
||||
case FigureData.TROUSERS:
|
||||
case FigureData.SHOES:
|
||||
case FigureData.TROUSER_ACCESSORIES:
|
||||
this._colors.set(k, _arg_2);
|
||||
this._colors.set(setType, colorIds);
|
||||
break;
|
||||
}
|
||||
|
||||
if(_arg_3) this.updateView();
|
||||
if(update) this.updateView();
|
||||
}
|
||||
|
||||
public getFigureStringWithFace(k: number, override = true): string
|
||||
@ -249,6 +253,18 @@
|
||||
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)
|
||||
{
|
||||
this._avatarEffectType = k;
|
||||
|
@ -4,7 +4,7 @@ import { AvatarEditorFigurePreviewViewProps } from './AvatarEditorFigurePreviewV
|
||||
|
||||
export const AvatarEditorFigurePreviewView: FC<AvatarEditorFigurePreviewViewProps> = props =>
|
||||
{
|
||||
const { editor = null } = props;
|
||||
const { figureData = null } = props;
|
||||
const [ updateId, setUpdateId ] = useState(-1);
|
||||
|
||||
const rerender = useCallback(() =>
|
||||
@ -14,15 +14,15 @@ export const AvatarEditorFigurePreviewView: FC<AvatarEditorFigurePreviewViewProp
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!editor) return;
|
||||
if(!figureData) return;
|
||||
|
||||
editor.notify = rerender;
|
||||
figureData.notify = rerender;
|
||||
|
||||
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 } />
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { AvatarEditor } from '../../common/AvatarEditor';
|
||||
import { FigureData } from '../../common/FigureData';
|
||||
|
||||
export interface AvatarEditorFigurePreviewViewProps
|
||||
{
|
||||
editor: AvatarEditor;
|
||||
figureData: FigureData;
|
||||
}
|
||||
|
@ -24,8 +24,9 @@ export const AvatarEditorFigureSetItemView: FC<AvatarEditorFigureSetItemViewProp
|
||||
})
|
||||
|
||||
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.isClear && <i className="icon clear-icon" /> }
|
||||
</NitroCardGridItemView>
|
||||
);
|
||||
}
|
||||
|
@ -1,17 +1,13 @@
|
||||
import { UserFigureComposer } from 'nitro-renderer';
|
||||
import { FC, useCallback, useEffect, useState } from 'react';
|
||||
import { SendMessageHook } from '../../../../hooks';
|
||||
import { LocalizeText } from '../../../../utils/LocalizeText';
|
||||
import { CategoryData } from '../../common/CategoryData';
|
||||
import { FigureData } from '../../common/FigureData';
|
||||
import { AvatarEditorFigurePreviewView } from '../figure-preview/AvatarEditorFigurePreviewView';
|
||||
import { AvatarEditorFigureSetView } from '../figure-set/AvatarEditorFigureSetView';
|
||||
import { AvatarEditorPaletteSetView } from '../palette-set/AvatarEditorPaletteSetView';
|
||||
import { AvatarEditorModelViewProps } from './AvatarEditorModelView.types';
|
||||
|
||||
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 [ maxPaletteCount, setMaxPaletteCount ] = useState(1);
|
||||
|
||||
@ -35,13 +31,6 @@ export const AvatarEditorModelView: FC<AvatarEditorModelViewProps> = props =>
|
||||
}
|
||||
}, [ model ]);
|
||||
|
||||
const saveFigure = useCallback(() =>
|
||||
{
|
||||
const figureData = editor.figureData;
|
||||
|
||||
SendMessageHook(new UserFigureComposer(figureData.gender, figureData.getFigureString()));
|
||||
}, [ editor ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
model.init();
|
||||
@ -58,49 +47,35 @@ export const AvatarEditorModelView: FC<AvatarEditorModelViewProps> = props =>
|
||||
|
||||
return (
|
||||
<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 &&
|
||||
<>
|
||||
<i className={ `icon male-icon ${ (editor.gender === FigureData.MALE) ? ' selected' : ''}` } onClick={ event => selectGender(FigureData.MALE) } />
|
||||
<i className={ `icon female-icon ${ (editor.gender === FigureData.FEMALE) ? ' selected' : ''}` } onClick={ event => selectGender(FigureData.FEMALE) } />
|
||||
<div className="d-flex justify-content-center align-items-center category-item cursor-pointer" onClick={ event => setGender(FigureData.MALE) }>
|
||||
<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 =>
|
||||
{
|
||||
const category = model.categories.get(name);
|
||||
|
||||
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 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 } />
|
||||
</div>
|
||||
<div className="col-3 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">
|
||||
<div className="col-5 d-flex flex-column h-100">
|
||||
{ (maxPaletteCount >= 1) &&
|
||||
<AvatarEditorPaletteSetView model={ model } category={ activeCategory } paletteSet={ activeCategory.getPalette(0) } paletteIndex={ 0 } /> }
|
||||
{ (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>
|
||||
);
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { AvatarEditor } from '../../common/AvatarEditor';
|
||||
import { Dispatch, SetStateAction } from 'react';
|
||||
import { IAvatarEditorCategoryModel } from '../../common/IAvatarEditorCategoryModel';
|
||||
|
||||
export interface AvatarEditorModelViewProps
|
||||
{
|
||||
model: IAvatarEditorCategoryModel;
|
||||
editor: AvatarEditor;
|
||||
selectGender: (gender: string) => void;
|
||||
gender: string;
|
||||
setGender: Dispatch<SetStateAction<string>>;
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import { AvatarEditorPaletteSetItemProps } from './AvatarEditorPaletteSetItem.ty
|
||||
|
||||
export const AvatarEditorPaletteSetItem: FC<AvatarEditorPaletteSetItemProps> = props =>
|
||||
{
|
||||
const { colorItem = null, onClick = null } = props;
|
||||
const { colorItem = null, ...rest } = props;
|
||||
const [ updateId, setUpdateId ] = useState(-1);
|
||||
|
||||
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 } />
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { DetailsHTMLAttributes } from 'react';
|
||||
import { AvatarEditorGridColorItem } from '../../common/AvatarEditorGridColorItem';
|
||||
|
||||
export interface AvatarEditorPaletteSetItemProps
|
||||
export interface AvatarEditorPaletteSetItemProps extends DetailsHTMLAttributes<HTMLDivElement>
|
||||
{
|
||||
colorItem: AvatarEditorGridColorItem;
|
||||
onClick: (item: AvatarEditorGridColorItem) => void;
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ import { AvatarEditorPaletteSetViewProps } from './AvatarEditorPaletteSetView.ty
|
||||
|
||||
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) =>
|
||||
{
|
||||
@ -19,10 +19,10 @@ export const AvatarEditorPaletteSetView: FC<AvatarEditorPaletteSetViewProps> = p
|
||||
}, [ model, category, paletteSet, paletteIndex ]);
|
||||
|
||||
return (
|
||||
<NitroCardGridView columns={ 3 } theme={ NitroCardGridThemes.THEME_SHADOWED }>
|
||||
<NitroCardGridView columns={ 4 } theme={ NitroCardGridThemes.THEME_SHADOWED } { ...rest }>
|
||||
{ (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>
|
||||
);
|
||||
|
@ -1,8 +1,9 @@
|
||||
import { DetailsHTMLAttributes } from 'react';
|
||||
import { AvatarEditorGridColorItem } from '../../common/AvatarEditorGridColorItem';
|
||||
import { CategoryData } from '../../common/CategoryData';
|
||||
import { IAvatarEditorCategoryModel } from '../../common/IAvatarEditorCategoryModel';
|
||||
|
||||
export interface AvatarEditorPaletteSetViewProps
|
||||
export interface AvatarEditorPaletteSetViewProps extends DetailsHTMLAttributes<HTMLDivElement>
|
||||
{
|
||||
model: IAvatarEditorCategoryModel;
|
||||
category: CategoryData;
|
||||
|