mirror of
https://github.com/billsonnn/nitro-react.git
synced 2024-11-23 14:40:50 +01:00
Update AvatarEditor
This commit is contained in:
parent
f69b40bbfe
commit
33aac67bbe
@ -75,70 +75,71 @@
|
||||
transform: scale(2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.wardrobe-grid {
|
||||
.nitro-wardrobe-grid {
|
||||
--nitro-grid-column-min-width: 80px !important;
|
||||
|
||||
.grid-item {
|
||||
height: 140px;
|
||||
max-height: 140px;
|
||||
background-color: $ghost;
|
||||
|
||||
&:after {
|
||||
position: absolute;
|
||||
content: '';
|
||||
top: 75%;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
border-radius: 50%;
|
||||
background-color: $gray-chateau;
|
||||
box-shadow: 0 0 8px 2px rgba($white,.6);
|
||||
transform: scale(2);
|
||||
}
|
||||
|
||||
.avatar-image {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
background-position-y: -23px !important;
|
||||
z-index: 4;
|
||||
}
|
||||
|
||||
.figure-button-container {
|
||||
background-color: $gray-chateau;
|
||||
z-index: 3;
|
||||
}
|
||||
}
|
||||
|
||||
.grid-item-container {
|
||||
height: 142px !important;
|
||||
max-height: 142px !important;
|
||||
|
||||
.grid-item {
|
||||
height: 140px;
|
||||
max-height: 140px;
|
||||
background-color: $ghost;
|
||||
|
||||
&:after {
|
||||
position: absolute;
|
||||
content: '';
|
||||
top: 75%;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
border-radius: 50%;
|
||||
background-color: $gray-chateau;
|
||||
box-shadow: 0 0 8px 2px rgba($white,.6);
|
||||
transform: scale(2);
|
||||
}
|
||||
|
||||
.avatar-image {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
background-position-y: -23px !important;
|
||||
z-index: 4;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.figure-button-container {
|
||||
background-color: $gray-chateau;
|
||||
z-index: 3;
|
||||
z-index: 2;
|
||||
}
|
||||
}
|
||||
|
||||
.grid-item-container {
|
||||
height: 142px !important;
|
||||
max-height: 142px !important;
|
||||
|
||||
.grid-item {
|
||||
background-color: $ghost;
|
||||
|
||||
.avatar-image {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
background-position-y: -23px !important;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.figure-button-container {
|
||||
background-color: $gray-chateau;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
&:after {
|
||||
position: absolute;
|
||||
content: '';
|
||||
height: 50%;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: $gray-chateau;
|
||||
box-shadow: 0 0 8px 2px rgba($white,.6);
|
||||
z-index: 1;
|
||||
}
|
||||
&:after {
|
||||
position: absolute;
|
||||
content: '';
|
||||
height: 50%;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: $gray-chateau;
|
||||
box-shadow: 0 0 8px 2px rgba($white,.6);
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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) =>
|
||||
{
|
||||
loadAvatarInEditor(figureData.getFigureStringWithFace(0, false), figureData.gender, false);
|
||||
resetCategories();
|
||||
}, [ figureData, loadAvatarInEditor, resetCategories ]);
|
||||
|
||||
const resetFigure = useCallback(() =>
|
||||
{
|
||||
loadAvatarInEditor(lastFigure, lastGender);
|
||||
resetCategories();
|
||||
}, [ lastFigure, lastGender, loadAvatarInEditor, resetCategories ]);
|
||||
|
||||
const randomizeFigure = useCallback(() =>
|
||||
{
|
||||
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)
|
||||
switch(action)
|
||||
{
|
||||
direction = (AvatarDirectionAngle.MAX_DIRECTION + (direction + 1));
|
||||
case AvatarEditorAction.ACTION_CLEAR:
|
||||
loadAvatarInEditor(figureData.getFigureStringWithFace(0, false), figureData.gender, false);
|
||||
resetCategories();
|
||||
return;
|
||||
case AvatarEditorAction.ACTION_RESET:
|
||||
loadAvatarInEditor(lastFigure, lastGender);
|
||||
resetCategories();
|
||||
return;
|
||||
case AvatarEditorAction.ACTION_RANDOMIZE:
|
||||
const figure = generateRandomFigure(figureData, figureData.gender, GetClubMemberLevel(), figureSetIds, [ FigureData.FACE ]);
|
||||
|
||||
loadAvatarInEditor(figure, figureData.gender, false);
|
||||
resetCategories();
|
||||
return;
|
||||
case AvatarEditorAction.ACTION_SAVE:
|
||||
SendMessageHook(new UserFigureComposer(figureData.gender, figureData.getFigureString()));
|
||||
setIsVisible(false);
|
||||
return;
|
||||
}
|
||||
|
||||
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()));
|
||||
setIsVisible(false);
|
||||
}, [ figureData ]);
|
||||
}, [ 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">
|
||||
<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>
|
||||
<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 } />
|
||||
<AvatarEditorFigureActionsView processAction={ processAction } />
|
||||
</NitroLayoutGridColumn>
|
||||
</NitroLayoutGrid>
|
||||
</NitroCardContentView>
|
||||
</NitroCardView>
|
||||
);
|
||||
|
7
src/views/avatar-editor/common/AvatarEditorAction.ts
Normal file
7
src/views/avatar-editor/common/AvatarEditorAction.ts
Normal 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';
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
|
||||
export interface AvatarEditorFigureActionsViewProps
|
||||
{
|
||||
processAction: (action: string) => void;
|
||||
}
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
@ -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" /> }
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
@ -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">
|
||||
{ figures }
|
||||
</NitroCardGridView>
|
||||
</div>
|
||||
</div>
|
||||
<NitroCardGridView className="nitro-wardrobe-grid">
|
||||
{ figures }
|
||||
</NitroCardGridView>
|
||||
);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user