Update AvatarEditor

This commit is contained in:
Bill 2021-09-29 22:30:25 -04:00
parent f69b40bbfe
commit 33aac67bbe
9 changed files with 180 additions and 142 deletions

View File

@ -75,8 +75,10 @@
transform: scale(2);
}
}
}
.wardrobe-grid {
.nitro-wardrobe-grid {
--nitro-grid-column-min-width: 80px !important;
.grid-item {
height: 140px;
@ -141,5 +143,4 @@
}
}
}
}
}

View File

@ -1,11 +1,12 @@
import { AvatarDirectionAngle, AvatarEditorFigureCategory, FigureSetIdsMessageEvent, GetWardrobeMessageComposer, IAvatarFigureContainer, UserFigureComposer, UserWardrobePageEvent } from '@nitrots/nitro-renderer';
import { AvatarEditorFigureCategory, FigureSetIdsMessageEvent, GetWardrobeMessageComposer, IAvatarFigureContainer, UserFigureComposer, UserWardrobePageEvent } from '@nitrots/nitro-renderer';
import { FC, useCallback, useEffect, useState } from 'react';
import { GetAvatarRenderManager, GetClubMemberLevel, GetSessionDataManager, LocalizeText } from '../../api';
import { AvatarEditorEvent } from '../../events/avatar-editor';
import { CreateMessageHook, SendMessageHook } from '../../hooks';
import { useUiEvent } from '../../hooks/events/ui/ui-event';
import { NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView } from '../../layout';
import { NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView, NitroLayoutGrid, NitroLayoutGridColumn } from '../../layout';
import { AvatarEditorViewProps } from './AvatarEditorView.types';
import { AvatarEditorAction } from './common/AvatarEditorAction';
import { AvatarEditorUtilities } from './common/AvatarEditorUtilities';
import { BodyModel } from './common/BodyModel';
import { FigureData } from './common/FigureData';
@ -14,6 +15,7 @@ import { HeadModel } from './common/HeadModel';
import { IAvatarEditorCategoryModel } from './common/IAvatarEditorCategoryModel';
import { LegModel } from './common/LegModel';
import { TorsoModel } from './common/TorsoModel';
import { AvatarEditorFigureActionsView } from './views/figure-actions/AvatarEditorFigureActionsView';
import { AvatarEditorFigurePreviewView } from './views/figure-preview/AvatarEditorFigurePreviewView';
import { AvatarEditorModelView } from './views/model/AvatarEditorModelView';
import { AvatarEditorWardrobeView } from './views/wardrobe/AvatarEditorWardrobeView';
@ -21,6 +23,10 @@ import { AvatarEditorWardrobeView } from './views/wardrobe/AvatarEditorWardrobeV
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';
const MAX_SAVED_FIGURES: number = 10;
const ACTION_CLEAR = 'action_clear';
const ACTION_RESET = 'action_reset';
const ACTION_RANDOMIZE = 'action_randomize';
const ACTION_SAVE = 'action_save';
export const AvatarEditorView: FC<AvatarEditorViewProps> = props =>
{
@ -157,46 +163,30 @@ export const AvatarEditorView: FC<AvatarEditorViewProps> = props =>
}
}, [ figures, figureData ]);
const clearFigure = useCallback(() =>
const processAction = useCallback((action: string) =>
{
switch(action)
{
case AvatarEditorAction.ACTION_CLEAR:
loadAvatarInEditor(figureData.getFigureStringWithFace(0, false), figureData.gender, false);
resetCategories();
}, [ figureData, loadAvatarInEditor, resetCategories ]);
const resetFigure = useCallback(() =>
{
return;
case AvatarEditorAction.ACTION_RESET:
loadAvatarInEditor(lastFigure, lastGender);
resetCategories();
}, [ lastFigure, lastGender, loadAvatarInEditor, resetCategories ]);
const randomizeFigure = useCallback(() =>
{
return;
case AvatarEditorAction.ACTION_RANDOMIZE:
const figure = generateRandomFigure(figureData, figureData.gender, GetClubMemberLevel(), figureSetIds, [ FigureData.FACE ]);
loadAvatarInEditor(figure, figureData.gender, false);
resetCategories();
}, [ figureData, figureSetIds, 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(() =>
{
return;
case AvatarEditorAction.ACTION_SAVE:
SendMessageHook(new UserFigureComposer(figureData.gender, figureData.getFigureString()));
setIsVisible(false);
}, [ figureData ]);
return;
}
}, [ figureData, lastFigure, lastGender, figureSetIds, loadAvatarInEditor, resetCategories ])
const setGender = useCallback((gender: string) =>
{
@ -295,37 +285,18 @@ export const AvatarEditorView: FC<AvatarEditorViewProps> = props =>
</NitroCardTabsItemView>
</NitroCardTabsView>
<NitroCardContentView>
<div className="row h-100">
<div className="col-9 d-flex flex-column h-100">
{ (activeCategory && !isWardrobeVisible) && <AvatarEditorModelView model={ activeCategory } gender={ figureData.gender } setGender={ setGender } /> }
{ isWardrobeVisible && <AvatarEditorWardrobeView figureData={ figureData } savedFigures={ savedFigures } setSavedFigures={ setSavedFigures } loadAvatarInEditor={ loadAvatarInEditor } /> }
</div>
<div className="col-3 d-flex flex-column h-100">
<div className="figure-preview-container">
<NitroLayoutGrid>
<NitroLayoutGridColumn size={ 9 }>
{ (activeCategory && !isWardrobeVisible) &&
<AvatarEditorModelView model={ activeCategory } gender={ figureData.gender } setGender={ setGender } /> }
{ isWardrobeVisible &&
<AvatarEditorWardrobeView figureData={ figureData } savedFigures={ savedFigures } setSavedFigures={ setSavedFigures } loadAvatarInEditor={ loadAvatarInEditor } /> }
</NitroLayoutGridColumn>
<NitroLayoutGridColumn overflow="hidden" size={ 3 }>
<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>
<button type="button" className="btn btn-sm btn-secondary" onClick={ randomizeFigure }>
<i className="fas fa-dice" />
</button>
</div>
<button type="button" className="btn btn-success btn-sm w-100" onClick={ saveFigure }>{ LocalizeText('avatareditor.save') }</button>
</div>
</div>
</div>
<AvatarEditorFigureActionsView processAction={ processAction } />
</NitroLayoutGridColumn>
</NitroLayoutGrid>
</NitroCardContentView>
</NitroCardView>
);

View File

@ -0,0 +1,7 @@
export class AvatarEditorAction
{
public static ACTION_SAVE: string = 'AEA_ACTION_SAVE';
public static ACTION_CLEAR: string = 'AEA_ACTION_CLEAR';
public static ACTION_RESET: string = 'AEA_ACTION_RESET';
public static ACTION_RANDOMIZE: string = 'AEA_ACTION_RANDOMIZE';
}

View File

@ -0,0 +1,29 @@
import { FC } from 'react';
import { LocalizeText } from '../../../../api';
import { NitroLayoutButton, NitroLayoutButtonGroup, NitroLayoutFlexColumn } from '../../../../layout';
import { AvatarEditorAction } from '../../common/AvatarEditorAction';
import { AvatarEditorFigureActionsViewProps } from './AvatarEditorFigureActionsView.types';
export const AvatarEditorFigureActionsView: FC<AvatarEditorFigureActionsViewProps> = props =>
{
const { processAction = null } = props;
return (
<NitroLayoutFlexColumn className="flex-grow-1" gap={ 2 }>
<NitroLayoutButtonGroup>
<NitroLayoutButton variant="secondary" size="sm" onClick={ event => processAction(AvatarEditorAction.ACTION_RESET) }>
<i className="fas fa-undo" />
</NitroLayoutButton>
<NitroLayoutButton variant="secondary" size="sm" onClick={ event => processAction(AvatarEditorAction.ACTION_CLEAR) }>
<i className="fas fa-trash" />
</NitroLayoutButton>
<NitroLayoutButton variant="secondary" size="sm" onClick={ event => processAction(AvatarEditorAction.ACTION_RANDOMIZE) }>
<i className="fas fa-dice" />
</NitroLayoutButton>
</NitroLayoutButtonGroup>
<NitroLayoutButton className="w-100" variant="success" size="sm" onClick={ event => processAction(AvatarEditorAction.ACTION_SAVE) }>
{ LocalizeText('avatareditor.save') }
</NitroLayoutButton>
</NitroLayoutFlexColumn>
)
}

View File

@ -0,0 +1,5 @@
export interface AvatarEditorFigureActionsViewProps
{
processAction: (action: string) => void;
}

View File

@ -1,4 +1,7 @@
import { AvatarDirectionAngle } from '@nitrots/nitro-renderer';
import { FC, useCallback, useEffect, useState } from 'react';
import { NitroLayoutFlexColumn } from '../../../../layout';
import { NitroLayoutBase } from '../../../../layout/base';
import { AvatarImageView } from '../../../shared/avatar-image/AvatarImageView';
import { AvatarEditorFigurePreviewViewProps } from './AvatarEditorFigurePreviewView.types';
@ -12,6 +15,21 @@ export const AvatarEditorFigurePreviewView: FC<AvatarEditorFigurePreviewViewProp
setUpdateId(prevValue => (prevValue + 1));
}, []);
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 ]);
useEffect(() =>
{
if(!figureData) return;
@ -24,5 +42,15 @@ export const AvatarEditorFigurePreviewView: FC<AvatarEditorFigurePreviewViewProp
}
}, [ figureData, rerender ] );
return <AvatarImageView figure={ figureData.getFigureString() } direction={ figureData.direction } scale={ 2 } />
return (
<NitroLayoutFlexColumn className="figure-preview-container" overflow="hidden" position="relative">
<AvatarImageView figure={ figureData.getFigureString() } direction={ figureData.direction } scale={ 2 } />
<NitroLayoutBase className="avatar-spotlight" />
<NitroLayoutBase className="avatar-shadow" />
<NitroLayoutBase 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) } />
</NitroLayoutBase>
</NitroLayoutFlexColumn>
);
}

View File

@ -24,7 +24,7 @@ export const AvatarEditorFigureSetItemView: FC<AvatarEditorFigureSetItemViewProp
})
return (
<NitroCardGridItemView itemImage={ (partItem.isClear ? undefined : partItem.imageUrl) } itemActive={ partItem.isSelected } onClick={ () => onClick(partItem) }>
<NitroCardGridItemView itemImage={ (partItem.isClear ? undefined : partItem.imageUrl) } itemActive={ partItem.isSelected } onClick={ event => onClick(partItem) }>
{ partItem.isHC && <CurrencyIcon className="position-absolute end-1 bottom-1" type={ 'hc' } /> }
{ partItem.isClear && <i className="icon clear-icon" /> }
{ partItem.isSellable && <i className="position-absolute icon sellable-icon end-1 bottom-1" /> }

View File

@ -1,4 +1,5 @@
import { FC, useCallback, useEffect, useState } from 'react';
import { NitroLayoutFlex, NitroLayoutGrid, NitroLayoutGridColumn } from '../../../../layout';
import { CategoryData } from '../../common/CategoryData';
import { FigureData } from '../../common/FigureData';
import { AvatarEditorFigureSetView } from '../figure-set/AvatarEditorFigureSetView';
@ -46,37 +47,37 @@ export const AvatarEditorModelView: FC<AvatarEditorModelViewProps> = props =>
if(!model || !activeCategory) return null;
return (
<div className="row h-100">
<div className="col-2 d-flex flex-column align-items-center h-100">
<NitroLayoutGrid>
<NitroLayoutGridColumn size={ 2 }>
{ model.canSetGender &&
<>
<div className="d-flex justify-content-center align-items-center category-item cursor-pointer" onClick={ event => setGender(FigureData.MALE) }>
<NitroLayoutFlex className="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) }>
</NitroLayoutFlex>
<NitroLayoutFlex className="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>
</NitroLayoutFlex>
</> }
{ !model.canSetGender && model.categories && (model.categories.size > 0) && Array.from(model.categories.keys()).map(name =>
{
const category = model.categories.get(name);
return (
<div key={ name } className="d-flex justify-content-center align-items-center category-item cursor-pointer" onClick={ event => selectCategory(name) }>
<NitroLayoutFlex key={ name } className="justify-content-center align-items-center category-item cursor-pointer" onClick={ event => selectCategory(name) }>
<i className={ `icon ${ category.name }-icon ${ (activeCategory === category) ? ' selected' : ''}` } />
</div>
</NitroLayoutFlex>
);
})}
</div>
<div className="col-5 d-flex flex-column h-100">
</NitroLayoutGridColumn>
<NitroLayoutGridColumn size={ 5 }>
<AvatarEditorFigureSetView model={ model } category={ activeCategory } setMaxPaletteCount={ setMaxPaletteCount } />
</div>
<div className="col-5 d-flex flex-column h-100 gap-2">
</NitroLayoutGridColumn>
<NitroLayoutGridColumn size={ 5 }>
{ (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 } className="mt-1" /> }
</div>
</div>
</NitroLayoutGridColumn>
</NitroLayoutGrid>
);
}

View File

@ -65,12 +65,8 @@ export const AvatarEditorWardrobeView: FC<AvatarEditorWardrobeViewProps> = props
}, [ savedFigures, saveFigureAtWardrobeIndex, wearFigureAtIndex ]);
return (
<div className="row h-100">
<div className="col-12 d-flex h-100">
<NitroCardGridView className="wardrobe-grid">
<NitroCardGridView className="nitro-wardrobe-grid">
{ figures }
</NitroCardGridView>
</div>
</div>
);
}