Update AvatarEditor

This commit is contained in:
Bill 2021-12-29 11:42:29 -05:00
parent 9ab3c28887
commit 84b8607b07
33 changed files with 106 additions and 171 deletions

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -192,7 +192,7 @@
}
&.spotlight {
&.spotlight-icon {
width: 130px;
height: 305px;
background-position: -5px -5px;
@ -287,71 +287,3 @@
}
}
}
.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 {
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;
}
}
}
}

View File

@ -1,6 +1,9 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
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 { Button } from '../../common/Button';
import { ButtonGroup } from '../../common/ButtonGroup';
import { Column } from '../../common/Column';
import { Grid } from '../../common/Grid';
import { AvatarEditorEvent } from '../../events/avatar-editor';
@ -16,7 +19,6 @@ 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';
@ -24,10 +26,6 @@ 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<{}> = props =>
{
@ -295,7 +293,22 @@ export const AvatarEditorView: FC<{}> = props =>
</Column>
<Column size={ 3 } overflow="hidden">
<AvatarEditorFigurePreviewView figureData={ figureData } />
<AvatarEditorFigureActionsView processAction={ processAction } />
<Column grow gap={ 2 }>
<ButtonGroup>
<Button variant="secondary" size="sm" onClick={ event => processAction(AvatarEditorAction.ACTION_RESET) }>
<FontAwesomeIcon icon="undo" />
</Button>
<Button variant="secondary" size="sm" onClick={ event => processAction(AvatarEditorAction.ACTION_CLEAR) }>
<FontAwesomeIcon icon="trash" />
</Button>
<Button variant="secondary" size="sm" onClick={ event => processAction(AvatarEditorAction.ACTION_RANDOMIZE) }>
<FontAwesomeIcon icon="dice" />
</Button>
</ButtonGroup>
<Button className="w-100" variant="success" size="sm" onClick={ event => processAction(AvatarEditorAction.ACTION_SAVE) }>
{ LocalizeText('avatareditor.save') }
</Button>
</Column>
</Column>
</Grid>
</NitroCardContentView>

View File

@ -1,9 +1,15 @@
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';
import { Base } from '../../../../common/Base';
import { Column } from '../../../../common/Column';
import { AvatarImageView } from '../../../../views/shared/avatar-image/AvatarImageView';
import { FigureData } from '../../common/FigureData';
import { AvatarEditorIcon } from '../AvatarEditorIcon';
export interface AvatarEditorFigurePreviewViewProps
{
figureData: FigureData;
}
export const AvatarEditorFigurePreviewView: FC<AvatarEditorFigurePreviewViewProps> = props =>
{
@ -43,14 +49,14 @@ export const AvatarEditorFigurePreviewView: FC<AvatarEditorFigurePreviewViewProp
}, [ figureData, rerender ] );
return (
<NitroLayoutFlexColumn className="figure-preview-container" overflow="hidden" position="relative">
<Column className="figure-preview-container" overflow="hidden" position="relative">
<AvatarImageView figure={ figureData.getFigureString() } direction={ figureData.direction } scale={ 2 } />
<NitroLayoutBase className="nitro-avatar-editor-spritesheet spotlight" />
<NitroLayoutBase className="avatar-shadow" />
<NitroLayoutBase className="arrow-container">
<div className="nitro-avatar-editor-spritesheet arrow-left-icon" onClick={ event => rotateFigure(figureData.direction + 1) } />
<div className="nitro-avatar-editor-spritesheet arrow-right-icon" onClick={ event => rotateFigure(figureData.direction - 1) } />
</NitroLayoutBase>
</NitroLayoutFlexColumn>
<AvatarEditorIcon icon="spotlight" />
<Base className="avatar-shadow" />
<Base className="arrow-container">
<AvatarEditorIcon pointer icon="arrow-left" onClick={ event => rotateFigure(figureData.direction + 1) } />
<AvatarEditorIcon pointer icon="arrow-right" onClick={ event => rotateFigure(figureData.direction - 1) } />
</Base>
</Column>
);
}

View File

@ -1,7 +1,7 @@
import { FC, useCallback, useEffect, useState } from 'react';
import { LayoutGridItem } from '../../../../common/layout/LayoutGridItem';
import { NitroCardGridItemViewProps } from '../../../../layout';
import { NitroCardGridItemView } from '../../../../layout/card/grid/item/NitroCardGridItemView';
import { CurrencyIcon } from '../../../shared/currency-icon/CurrencyIcon';
import { CurrencyIcon } from '../../../../views/shared/currency-icon/CurrencyIcon';
import { AvatarEditorGridPartItem } from '../../common/AvatarEditorGridPartItem';
import { AvatarEditorIcon } from '../AvatarEditorIcon';
@ -31,11 +31,11 @@ export const AvatarEditorFigureSetItemView: FC<AvatarEditorFigureSetItemViewProp
})
return (
<NitroCardGridItemView itemImage={ (partItem.isClear ? undefined : partItem.imageUrl) } itemActive={ partItem.isSelected } { ...rest }>
<LayoutGridItem itemImage={ (partItem.isClear ? undefined : partItem.imageUrl) } itemActive={ partItem.isSelected } { ...rest }>
{ partItem.isHC && <CurrencyIcon className="position-absolute end-1 bottom-1" type={ 'hc' } /> }
{ partItem.isClear && <AvatarEditorIcon icon="clear" /> }
{ partItem.isSellable && <AvatarEditorIcon icon="sellable" position="absolute" className="end-1 bottom-1" /> }
{ children }
</NitroCardGridItemView>
</LayoutGridItem>
);
}

View File

@ -30,7 +30,7 @@ export const AvatarEditorFigureSetView: FC<AvatarEditorFigureSetViewProps> = pro
}, [ model, category, setMaxPaletteCount ]);
return (
<Grid grow columnCount={ 3 } overflow="auto">
<Grid grow columnCount={ 3 } columnMinHeight={ 50 } overflow="auto">
{ (category.parts.length > 0) && category.parts.map((item, index) =>
<AvatarEditorFigureSetItemView key={ index } partItem={ item } onClick={ event => selectPart(item) } />) }
</Grid>

View File

@ -1,10 +1,9 @@
import { FC, useCallback, useEffect, useState } from 'react';
import { NitroCardGridItemViewProps } from '../../../../layout';
import { NitroCardGridItemView } from '../../../../layout/card/grid/item/NitroCardGridItemView';
import { CurrencyIcon } from '../../../shared/currency-icon/CurrencyIcon';
import { LayoutGridItem, LayoutGridItemProps } from '../../../../common/layout/LayoutGridItem';
import { CurrencyIcon } from '../../../../views/shared/currency-icon/CurrencyIcon';
import { AvatarEditorGridColorItem } from '../../common/AvatarEditorGridColorItem';
export interface AvatarEditorPaletteSetItemProps extends NitroCardGridItemViewProps
export interface AvatarEditorPaletteSetItemProps extends LayoutGridItemProps
{
colorItem: AvatarEditorGridColorItem;
}
@ -27,9 +26,9 @@ export const AvatarEditorPaletteSetItem: FC<AvatarEditorPaletteSetItemProps> = p
});
return (
<NitroCardGridItemView itemColor={ colorItem.color } itemActive={ colorItem.isSelected } { ...rest }>
<LayoutGridItem itemColor={ colorItem.color } itemActive={ colorItem.isSelected } { ...rest }>
{ colorItem.isHC && <CurrencyIcon className="position-absolute end-1 bottom-1" type={ 'hc' } /> }
{ children }
</NitroCardGridItemView>
</LayoutGridItem>
);
}

View File

@ -27,7 +27,7 @@ export const AvatarEditorPaletteSetView: FC<AvatarEditorPaletteSetViewProps> = p
}, [ model, category, paletteSet, paletteIndex ]);
return (
<Grid grow columnCount={ 3 } overflow="auto">
<Grid grow columnCount={ 4 } columnMinWidth={ 30 } overflow="auto">
{ (paletteSet.length > 0) && paletteSet.map((item, index) =>
<AvatarEditorPaletteSetItem key={ index } colorItem={ item } onClick={ event => selectColor(item) } />) }
</Grid>

View File

@ -3,10 +3,10 @@ import { Dispatch, FC, SetStateAction, useCallback, useMemo } from 'react';
import { Button } from 'react-bootstrap';
import { GetAvatarRenderManager, GetSessionDataManager } from '../../../../api';
import { Grid } from '../../../../common/Grid';
import { LayoutGridItem } from '../../../../common/layout/LayoutGridItem';
import { SendMessageHook } from '../../../../hooks';
import { NitroCardGridItemView } from '../../../../layout/card/grid/item/NitroCardGridItemView';
import { AvatarImageView } from '../../../shared/avatar-image/AvatarImageView';
import { CurrencyIcon } from '../../../shared/currency-icon/CurrencyIcon';
import { AvatarImageView } from '../../../../views/shared/avatar-image/AvatarImageView';
import { CurrencyIcon } from '../../../../views/shared/currency-icon/CurrencyIcon';
import { FigureData } from '../../common/FigureData';
export interface AvatarEditorWardrobeViewProps
@ -58,14 +58,15 @@ export const AvatarEditorWardrobeView: FC<AvatarEditorWardrobeViewProps> = props
if(figureContainer) clubLevel = GetAvatarRenderManager().getFigureClubLevel(figureContainer, gender);
items.push(
<NitroCardGridItemView key={ index } className="flex-column justify-content-end position-relative">
{ figureContainer && <AvatarImageView figure={ figureContainer.getFigureString() } gender={ gender } direction={ 2 } /> }
<LayoutGridItem key={ index } position="relative" justifyContent="end" alignItems="center">
{ figureContainer &&
<AvatarImageView figure={ figureContainer.getFigureString() } gender={ gender } direction={ 2 } position="absolute" className="bottom-3" /> }
{ (clubLevel > 0) && <CurrencyIcon className="position-absolute top-1 start-1" type="hc" /> }
<div className="d-flex w-100 figure-button-container p-1">
<Button variant="link" size="sm" className="w-100" onClick={ event => saveFigureAtWardrobeIndex(index) }>Save</Button>
{ figureContainer && <Button variant="link" size="sm" className="w-100" onClick={ event => wearFigureAtIndex(index) } disabled={ (clubLevel > GetSessionDataManager().clubLevel) }>Use</Button> }
</div>
</NitroCardGridItemView>
</LayoutGridItem>
);
});
@ -73,7 +74,7 @@ export const AvatarEditorWardrobeView: FC<AvatarEditorWardrobeViewProps> = props
}, [ savedFigures, saveFigureAtWardrobeIndex, wearFigureAtIndex ]);
return (
<Grid grow columnCount={ 5 } overflow="auto" columnMinWidth={ 80 }>
<Grid grow columnCount={ 5 } overflow="auto" columnMinWidth={ 80 } columnMinHeight={ 140 }>
{ figures }
</Grid>
);

View File

@ -1 +1,2 @@
@import './avatar-editor/AvatarEditorView';
@import './inventory/InventoryView';

View File

@ -1,11 +1,9 @@
@import "./shared/Shared";
@import "./avatar-editor/AvatarEditorView";
@import "./camera/CameraWidgetView";
@import "./catalog/CatalogView";
@import "./friends/FriendsView";
@import "./groups/GroupView";
@import "./hotel-view/HotelView";
@import "./inventory/InventoryView";
@import "./loading/LoadingView";
@import "./main/MainView";
@import "./navigator/NavigatorView";

View File

@ -1,29 +0,0 @@
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

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

View File

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

View File

@ -1,11 +1,11 @@
import { HabboWebTools, RoomSessionEvent } from '@nitrots/nitro-renderer';
import { FC, useCallback, useEffect, useState } from 'react';
import { AddEventLinkTracker, GetCommunication, RemoveLinkEventTracker } from '../../api';
import { AvatarEditorView } from '../../components/avatar-editor/AvatarEditorView';
import { InventoryView } from '../../components/inventory/InventoryView';
import { useRoomSessionManagerEvent } from '../../hooks/events/nitro/session/room-session-manager-event';
import { TransitionAnimation, TransitionAnimationTypes } from '../../layout';
import { AchievementsView } from '../achievements/AchievementsView';
import { AvatarEditorView } from '../avatar-editor/AvatarEditorView';
import { CameraWidgetView } from '../camera/CameraWidgetView';
import { CampaignView } from '../campaign/CampaignView';
import { CatalogView } from '../catalog/CatalogView';

View File

@ -1,9 +1,9 @@
import { RedeemItemClothingComposer, RoomObjectCategory, UserFigureComposer } from '@nitrots/nitro-renderer';
import { FC, useCallback, useEffect, useState } from 'react';
import { GetAvatarRenderManager, GetConnection, GetFurnitureDataForRoomObject, GetSessionDataManager, LocalizeText } from '../../../../../../../api';
import { FigureData } from '../../../../../../../components/avatar-editor/common/FigureData';
import { FurniCategory } from '../../../../../../../components/inventory/common/FurniCategory';
import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../../../../layout';
import { FigureData } from '../../../../../../avatar-editor/common/FigureData';
import { AvatarImageView } from '../../../../../../shared/avatar-image/AvatarImageView';
import { useRoomContext } from '../../../../../context/RoomContext';
import { PurchasableClothingConfirmViewProps } from './PurchasableClothingConfirmView.types';

View File

@ -1,27 +1,62 @@
import { AvatarScaleType, AvatarSetType } from '@nitrots/nitro-renderer';
import { FC, useEffect, useMemo, useRef, useState } from 'react';
import { CSSProperties, FC, useEffect, useMemo, useRef, useState } from 'react';
import { GetAvatarRenderManager } from '../../../api';
import { AvatarImageViewProps } from './AvatarImageView.types';
import { Base, BaseProps } from '../../../common/Base';
export interface AvatarImageViewProps extends BaseProps<HTMLDivElement>
{
figure: string;
gender?: string;
headOnly?: boolean;
direction?: number;
scale?: number;
}
export const AvatarImageView: FC<AvatarImageViewProps> = props =>
{
const { figure = '', gender = 'M', headOnly = false, direction = 0, scale = 1 } = props;
const { figure = '', gender = 'M', headOnly = false, direction = 0, scale = 1, classNames = [], style = {}, ...rest } = props;
const [ avatarUrl, setAvatarUrl ] = useState<string>(null);
const [ randomValue, setRandomValue ] = useState(-1);
const isDisposed = useRef(false);
const getScaleStyle = useMemo(() =>
const getClassNames = useMemo(() =>
{
if(scale === .5) return '0-5';
const newClassNames: string[] = [ 'avatar-image' ];
if(scale === .75) return '0-75';
switch(scale)
{
case .5:
newClassNames.push('scale-0-5');
break;
case .75:
newClassNames.push('scale-0-75');
break;
case 1.25:
newClassNames.push('scale-1-25');
break;
case 1.50:
newClassNames.push('scale-1-50');
break;
default:
newClassNames.push(`scale-${ scale }`);
break;
}
if(scale === 1.25) return '1-25';
if(classNames.length) newClassNames.push(...classNames);
if(scale === 1.50) return '1-50';
return newClassNames;
}, [ scale, classNames ]);
return scale.toString();
}, [ scale ]);
const getStyle = useMemo(() =>
{
let newStyle: CSSProperties = {};
if(avatarUrl && avatarUrl.length) newStyle.backgroundImage = `url('${ avatarUrl }')`;
if(Object.keys(style).length) newStyle = { ...newStyle, ...style };
return newStyle;
}, [ avatarUrl, style ]);
useEffect(() =>
{
@ -60,8 +95,6 @@ export const AvatarImageView: FC<AvatarImageViewProps> = props =>
isDisposed.current = true;
}
}, []);
const url = `url('${ avatarUrl }')`;
return <div className={ 'avatar-image scale-' + getScaleStyle } style={ (avatarUrl && url.length) ? { backgroundImage: url } : {} }></div>;
return <Base classNames={ getClassNames } style={ getStyle } { ...rest } />;
}

View File

@ -1,8 +0,0 @@
export interface AvatarImageViewProps
{
figure: string;
gender?: string;
headOnly?: boolean;
direction?: number;
scale?: number;
}