Fix: Avatar Editor

This commit is contained in:
duckietm 2024-03-05 15:30:27 +01:00
parent 35b5791dad
commit d7de6777be
33 changed files with 605 additions and 213 deletions

View File

@ -21,8 +21,8 @@ $toolbar-height: 55px;
$achievement-width: 375px; $achievement-width: 375px;
$achievement-height: 405px; $achievement-height: 405px;
$avatar-editor-width: 746px; $avatar-editor-width: 520px;
$avatar-editor-height: 445px; $avatar-editor-height: 553px;
$catalog-width: 650px; $catalog-width: 650px;
$catalog-height: 480px; $catalog-height: 480px;

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 225 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 330 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 231 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 247 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 256 B

View File

@ -1,5 +1,5 @@
$nitro-card-header-height: 33px; $nitro-card-header-height: 33px;
$nitro-card-tabs-height: 33px; $nitro-card-tabs-height: 42px;
.nitro-card { .nitro-card {
resize: both; resize: both;

View File

@ -0,0 +1,75 @@
import { FC, useMemo } from 'react';
import { Base } from '../Base';
import { Column, ColumnProps } from '../Column';
import { LayoutItemCountView } from './LayoutItemCountView';
import { LayoutLimitedEditionStyledNumberView } from './limited-edition';
export interface LayoutGridColorPickerItemProps extends ColumnProps
{
itemImage?: string;
itemColor?: string;
itemActive?: boolean;
itemCount?: number;
itemCountMinimum?: number;
itemUniqueSoldout?: boolean;
itemUniqueNumber?: number;
itemUnseen?: boolean;
itemHighlight?: boolean;
disabled?: boolean;
}
export const LayoutGridColorPickerItem: FC<LayoutGridColorPickerItemProps> = props =>
{
const { itemImage = undefined, itemColor = undefined, itemActive = false, itemCount = 1, itemCountMinimum = 1, itemUniqueSoldout = false, itemUniqueNumber = -2, itemUnseen = false, itemHighlight = false, disabled = false, center = true, column = true, style = {}, classNames = [], position = 'relative', overflow = 'hidden', children = null, ...rest } = props;
const getClassNames = useMemo(() =>
{
const newClassNames: string[] = [ 'layout-grid-item', 'color-picker-frame' ];
if(itemActive) newClassNames.push('active');
if(itemUniqueSoldout || (itemUniqueNumber > 0)) newClassNames.push('unique-item');
if(itemUniqueSoldout) newClassNames.push('sold-out');
if(itemUnseen) newClassNames.push('unseen');
if(itemHighlight) newClassNames.push('has-highlight');
if(disabled) newClassNames.push('disabled')
if(itemImage === null) newClassNames.push('icon', 'loading-icon');
if(classNames.length) newClassNames.push(...classNames);
return newClassNames;
}, [ itemActive, itemUniqueSoldout, itemUniqueNumber, itemUnseen, itemHighlight, disabled, itemImage, classNames ]);
const getStyle = useMemo(() =>
{
let newStyle = { ...style };
if(itemImage) newStyle.backgroundImage = `url(${ itemImage })`;
if(itemColor) newStyle.backgroundColor = itemColor;
if(Object.keys(style).length) newStyle = { ...newStyle, ...style };
return newStyle;
}, [ style, itemImage, itemColor ]);
return (
<Column center={ center } pointer position={ position } overflow={ overflow } column={ column } classNames={ getClassNames } style={ getStyle } { ...rest }>
{ (itemCount > itemCountMinimum) &&
<LayoutItemCountView count={ itemCount } /> }
{ (itemUniqueNumber > 0) &&
<>
<Base fit className="unique-bg-override" style={ { backgroundImage: `url(${ itemImage })` } } />
<div className="position-absolute bottom-0 unique-item-counter">
<LayoutLimitedEditionStyledNumberView value={ itemUniqueNumber } />
</div>
</> }
{ children }
</Column>
);
}

View File

@ -18,7 +18,7 @@
height: 25px; height: 25px;
background-position: -226px -61px; background-position: -226px -61px;
&.selected { &.selected, &:hover {
width: 25px; width: 25px;
height: 25px; height: 25px;
background-position: -226px -96px; background-position: -226px -96px;
@ -30,7 +30,7 @@
height: 29px; height: 29px;
background-position: -145px -5px; background-position: -145px -5px;
&.selected { &.selected, &:hover {
width: 31px; width: 31px;
height: 29px; height: 29px;
background-position: -145px -44px; background-position: -145px -44px;
@ -42,7 +42,7 @@
height: 24px; height: 24px;
background-position: -186px -39px; background-position: -186px -39px;
&.selected { &.selected, &:hover {
width: 29px; width: 29px;
height: 24px; height: 24px;
background-position: -186px -73px; background-position: -186px -73px;
@ -60,7 +60,7 @@
height: 24px; height: 24px;
background-position: -145px -264px; background-position: -145px -264px;
&.selected { &.selected, &:hover {
width: 30px; width: 30px;
height: 24px; height: 24px;
background-position: -186px -5px; background-position: -186px -5px;
@ -73,7 +73,7 @@
height: 16px; height: 16px;
background-position: -226px -193px; background-position: -226px -193px;
&.selected { &.selected, &:hover {
width: 35px; width: 35px;
height: 16px; height: 16px;
background-position: -226px -219px; background-position: -226px -219px;
@ -85,7 +85,7 @@
height: 20px; height: 20px;
background-position: -186px -137px; background-position: -186px -137px;
&.selected { &.selected, &:hover {
width: 27px; width: 27px;
height: 20px; height: 20px;
background-position: -186px -107px; background-position: -186px -107px;
@ -97,7 +97,7 @@
height: 27px; height: 27px;
background-position: -186px -202px; background-position: -186px -202px;
&.selected { &.selected, &:hover {
width: 18px; width: 18px;
height: 27px; height: 27px;
background-position: -186px -239px; background-position: -186px -239px;
@ -109,7 +109,7 @@
height: 22px; height: 22px;
background-position: -226px -245px; background-position: -226px -245px;
&.selected { &.selected, &:hover {
width: 25px; width: 25px;
height: 22px; height: 22px;
background-position: -226px -277px; background-position: -226px -277px;
@ -121,7 +121,7 @@
height: 27px; height: 27px;
background-position: -145px -83px; background-position: -145px -83px;
&.selected { &.selected, &:hover {
width: 31px; width: 31px;
height: 27px; height: 27px;
background-position: -145px -120px; background-position: -145px -120px;
@ -133,7 +133,7 @@
height: 25px; height: 25px;
background-position: -145px -194px; background-position: -145px -194px;
&.selected { &.selected, &:hover {
width: 29px; width: 29px;
height: 25px; height: 25px;
background-position: -145px -229px; background-position: -145px -229px;
@ -145,7 +145,7 @@
height: 20px; height: 20px;
background-position: -303px -45px; background-position: -303px -45px;
&.selected { &.selected, &:hover {
width: 19px; width: 19px;
height: 20px; height: 20px;
background-position: -303px -75px; background-position: -303px -75px;
@ -164,7 +164,7 @@
height: 21px; height: 21px;
background-position: -186px -276px; background-position: -186px -276px;
&.selected { &.selected, &:hover {
width: 21px; width: 21px;
height: 21px; height: 21px;
background-position: -272px -5px; background-position: -272px -5px;
@ -184,7 +184,7 @@
height: 10px; height: 10px;
background-position: -303px -5px; background-position: -303px -5px;
&.selected { &.selected, &:hover {
width: 37px; width: 37px;
height: 10px; height: 10px;
background-position: -303px -25px; background-position: -303px -25px;
@ -204,7 +204,7 @@
height: 18px; height: 18px;
background-position: -226px -5px; background-position: -226px -5px;
&.selected { &.selected, &:hover {
width: 36px; width: 36px;
height: 18px; height: 18px;
background-position: -226px -33px; background-position: -226px -33px;
@ -212,18 +212,64 @@
} }
} }
.nitro-avatar-editor-wardrobe-figure-preview { .saved-outfits-title {
background-color: $pale-sky; color: #a7a6a2;
overflow: hidden; font-weight: bold;
z-index: 1; }
.saved-outfit-container {
display: flex;
width: 100% !important;
height: 91.5%;
.avatar-image { .avatar-image {
position: absolute; width: 40px !important;
bottom: -15px;
margin: 0 auto;
z-index: 4; z-index: 4;
transform: scale(0.5);
} }
.avatar-figure {
margin-top: -46px;
margin-left: -9px;
image-rendering: auto !important;
}
.nitro-avatar-editor-wardrobe-container {
background-color: #cacaca;
border-radius: 0.3rem;
border: solid 1px #000;
height: 386px;
width: 100%;
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-gap: 10px;
padding: 10px 12px 10px 0;
overflow-y: auto;
}
.avatar-container {
height: 50px;
border-radius: 0.3rem;
background-color: #a7a6a2;
width: 30px;
}
.saved-outfit-button {
margin-top: -3px;
background-color: transparent;
border: none;
}
}
.nitro-avatar-editor-wardrobe-figure-preview {
border-image-source: url(@/assets/images/avatareditor/wardrobe_user_bg.png);
border-image-slice: 4 4 4 4 fill;
border-image-width: 4px 4px 4px 4px;
background-color: transparent;
overflow: hidden;
z-index: 1;
.avatar-shadow { .avatar-shadow {
position: absolute; position: absolute;
left: 0; left: 0;
@ -237,19 +283,6 @@
z-index: 2; z-index: 2;
} }
&:after {
position: absolute;
content: '';
top: 75%;
bottom: 0;
left: 0;
right: 0;
border-radius: 50%;
background-color: $pale-sky;
box-shadow: 0 0 8px 2px rgba($white,.6);
transform: scale(2);
}
.button-container { .button-container {
position: absolute; position: absolute;
bottom: 0; bottom: 0;
@ -258,8 +291,11 @@
} }
.nitro-avatar-editor { .nitro-avatar-editor {
width: $avatar-editor-width;
height: $avatar-editor-height; height: $avatar-editor-height;
min-width: $avatar-editor-width;
min-height: $avatar-editor-height;
max-width: $avatar-editor-width;
max-height: $avatar-editor-height;
.category-item { .category-item {
height: 40px; height: 40px;
@ -268,7 +304,6 @@
.figure-preview-container { .figure-preview-container {
position: relative; position: relative;
height: 100%; height: 100%;
background-color: #b69b83;
overflow: hidden; overflow: hidden;
z-index: 1; z-index: 1;
@ -279,9 +314,17 @@
padding: 0 10px; padding: 0 10px;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
bottom: 12px; bottom: 50px;
z-index: 5; z-index: 5;
.arrow-left {
background-image: url(@/assets/images/avatareditor/rotation_arrow.png);
width: 44px;
height: 29px;
margin-left: auto;
margin-right: auto;
}
.icon { .icon {
cursor: pointer; cursor: pointer;
} }
@ -291,46 +334,194 @@
position: absolute; position: absolute;
left: 0; left: 0;
right: 0; right: 0;
bottom: 50px; bottom: 125px;
margin: 0 auto; margin: 0 auto;
z-index: 4; z-index: 4;
} }
.avatar-spotlight {
position: absolute;
top: -10px;
left: 0;
right: 0;
margin: 0 auto;
opacity: 0.3;
pointer-events: none;
z-index: 3;
}
.avatar-shadow { .avatar-shadow {
position: absolute; position: absolute;
left: 0; left: 0;
right: 0; right: 0;
bottom: 15px; bottom: 88px;
width: 70px; width: 68px;
height: 30px; height: 34px;
margin: 0 auto; margin: 0 auto;
border-radius: 100%; background: url(@/assets/images/avatareditor/avatar_shadow.png);
background-color: rgba(0, 0, 0, 0.20); z-index: 2;
}
}
}
.nitro-avatar-editor.expanded {
max-width: $avatar-editor-width + 264px;
min-width: $avatar-editor-width + 164px;
}
.choose-clothing {
width: 320px;
}
.color-picker-frame {
border-image-source: url(@/assets/images/avatareditor/color_frame.png);
border-image-slice: 6 6 6 6 fill;
border-image-width: 6px 6px 6px 6px;
width: 14px;
height: 21px;
border-radius: 4px;
&.active {
border-image-source: url(@/assets/images/avatareditor/color_frame_active.png);
height: 21px;
margin-top: -2px;
}
&:hover {
border-image-source: url(@/assets/images/avatareditor/color_frame_active.png);
height: 21px;
margin-top: -2px;
}
}
.hc-icon {
background-image: url(@/assets/images/avatareditor/hc_icon.png);
height: 9px;
width: 10px;
bottom: 2px;
left: 2px;
}
.avatar-editor-tabs {
position: relative;
.tab {
background-position-x: 0;
background-position-y: 0;
width: 34px;
height: 22px;
}
.hd {
background: url(@/assets/images/wardrobe/hd.png) no-repeat center;
}
.head {
background: url(@/assets/images/wardrobe/head.png) no-repeat center;
}
.torso {
background: url(@/assets/images/wardrobe/torso.png) no-repeat center;
}
.legs {
background: url(@/assets/images/wardrobe/legs.png) no-repeat center;
}
.tab-wardrobe {
width: 40px;
height: 28px;
background-size: 38px 28px;
background-image: url(@/assets/images/wardrobe/wardrobe.png);
background-repeat: no-repeat;
background-position: center;
filter: contrast(1.2) brightness(1.05);
}
.nav-tabs .nav-link {
position: relative;
border-image-source: url(@/assets/images/boxes/card/tabs_avatareditor.png);
border-image-slice: 7 7 7 7 fill;
border-image-width: 7px 7px 7px 7px;
border-image-outset: 0px 0px 0px 0px;
border-image-repeat: repeat repeat;
margin-bottom: -2px;
margin-left: -2px;
&:hover {
border-image-source: url(@/assets/images/boxes/card/tabs_active.png);
}
}
.nav-tabs .nav-link.active {
border-image-source: url(@/assets/images/boxes/card/tabs_active.png);
}
}
.randomize-container {
bottom: 95px;
left: 330px;
z-index: 2; z-index: 2;
} }
&:after { .randomize-icon {
position: absolute; background-image: url(@/assets/images/avatareditor/randomize_transparent.png);
content: ''; height: 33px;
top: 75%; width: 39px;
bottom: 0;
left: 0; &:hover {
right: 0; background-image: url(@/assets/images/avatareditor/randomize.png);
border-radius: 50%;
background-color: #a68d76;
box-shadow: 0 0 8px 2px rgba($white,.6);
transform: scale(2);
} }
} }
.avatar-wardrobe {
border-image-source: url(@/assets/images/avatareditor/wardrobe_bg.png);
border-image-slice: 6 6 6 6 fill;
border-image-width: 6px 6px 6px 6px;
}
.avatar-container {
padding: 3px;
}
.avatar-parts {
border: none !important;
height: 42px;
width: 42px;
background-position: center;
background-repeat: no-repeat;
border-radius: 2rem !important;
overflow: visible !important;
background-color: transparent;
&:hover {
box-shadow: 0 0 0 3px #dbdad5 !important;
background-color: #cecdc8 !important;
}
&:active,
&.part-selected {
box-shadow: 0 0 0 3px #c5c3c0 !important;
background-color: #b1b1b1 !important;
}
}
.avatar-parts-container {
height: 70%;
padding-left: 10px;
}
.avatar-color-palette-container {
height: 30%;
width: 100%!important;
padding-left: 10px;
}
.dual-palette {
display: flex !important;
flex-direction: row !important;
}
.avatar-editor-palette-set-view {
padding-right: 15px !important;
flex-grow: 1;
}
.clothing-container {
padding-right: 15px !important;
}
.action-buttons {
gap: 5px;
} }

View File

@ -1,8 +1,8 @@
import { AvatarEditorFigureCategory, FigureSetIdsMessageEvent, GetWardrobeMessageComposer, IAvatarFigureContainer, ILinkEventTracker, UserFigureComposer, UserWardrobePageEvent } from '@nitrots/nitro-renderer'; import { AvatarEditorFigureCategory, FigureSetIdsMessageEvent, GetWardrobeMessageComposer, IAvatarFigureContainer, ILinkEventTracker, SetClothingChangeDataMessageComposer, UserFigureComposer, UserWardrobePageEvent } from '@nitrots/nitro-renderer';
import { FC, useCallback, useEffect, useMemo, useState } from 'react'; import { FC, useCallback, useEffect, useMemo, useState } from 'react';
import { FaDice, FaTrash, FaUndo } from 'react-icons/fa'; import { FaDice, FaTrash, FaUndo } from 'react-icons/fa';
import { AddEventLinkTracker, AvatarEditorAction, AvatarEditorUtilities, BodyModel, FigureData, generateRandomFigure, GetAvatarRenderManager, GetClubMemberLevel, GetConfiguration, GetSessionDataManager, HeadModel, IAvatarEditorCategoryModel, LegModel, LocalizeText, RemoveLinkEventTracker, SendMessageComposer, TorsoModel } from '../../api'; import { AddEventLinkTracker, AvatarEditorAction, AvatarEditorUtilities, BodyModel, FigureData, GetAvatarRenderManager, GetClubMemberLevel, GetConfiguration, GetSessionDataManager, HeadModel, IAvatarEditorCategoryModel, LegModel, LocalizeText, RemoveLinkEventTracker, SendMessageComposer, SetLocalStorage, TorsoModel, generateRandomFigure } from '../../api';
import { Button, ButtonGroup, Column, Grid, NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView } from '../../common'; import { Button, ButtonGroup, Column, Flex, Grid, NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView } from '../../common';
import { useMessageEvent } from '../../hooks'; import { useMessageEvent } from '../../hooks';
import { AvatarEditorFigurePreviewView } from './views/AvatarEditorFigurePreviewView'; import { AvatarEditorFigurePreviewView } from './views/AvatarEditorFigurePreviewView';
import { AvatarEditorModelView } from './views/AvatarEditorModelView'; import { AvatarEditorModelView } from './views/AvatarEditorModelView';
@ -26,9 +26,20 @@ export const AvatarEditorView: FC<{}> = props =>
const [ lastGender, setLastGender ] = useState<string>(null); const [ lastGender, setLastGender ] = useState<string>(null);
const [ needsReset, setNeedsReset ] = useState(true); const [ needsReset, setNeedsReset ] = useState(true);
const [ isInitalized, setIsInitalized ] = useState(false); const [ isInitalized, setIsInitalized ] = useState(false);
const [ genderFootballGate, setGenderFootballGate ] = useState<string>(null);
const [ objectFootballGate, setObjectFootballGate ] = useState<number>(null);
const DEFAULT_MALE_FOOTBALL_GATE = JSON.parse(window.localStorage.getItem('nitro.look.footballgate.M')) || 'ch-3109-92-1408.lg-3116-82-1408.sh-3115-1408-1408';
const DEFAULT_FEMALE_FOOTBALL_GATE = JSON.parse(window.localStorage.getItem('nitro.look.footballgate.F')) || 'ch-3112-1408-1408.lg-3116-71-1408.sh-3115-1408-1408';
const maxWardrobeSlots = useMemo(() => GetConfiguration<number>('avatar.wardrobe.max.slots', 10), []); const maxWardrobeSlots = useMemo(() => GetConfiguration<number>('avatar.wardrobe.max.slots', 10), []);
const onClose = () =>
{
setGenderFootballGate(null);
setObjectFootballGate(null);
setIsVisible(false);
}
useMessageEvent<FigureSetIdsMessageEvent>(FigureSetIdsMessageEvent, event => useMessageEvent<FigureSetIdsMessageEvent>(FigureSetIdsMessageEvent, event =>
{ {
const parser = event.getParser(); const parser = event.getParser();
@ -72,13 +83,21 @@ export const AvatarEditorView: FC<{}> = props =>
{ {
const categories = new Map(); const categories = new Map();
if (!genderFootballGate)
{
categories.set(AvatarEditorFigureCategory.GENERIC, new BodyModel()); categories.set(AvatarEditorFigureCategory.GENERIC, new BodyModel());
categories.set(AvatarEditorFigureCategory.HEAD, new HeadModel()); categories.set(AvatarEditorFigureCategory.HEAD, new HeadModel());
categories.set(AvatarEditorFigureCategory.TORSO, new TorsoModel()); categories.set(AvatarEditorFigureCategory.TORSO, new TorsoModel());
categories.set(AvatarEditorFigureCategory.LEGS, new LegModel()); categories.set(AvatarEditorFigureCategory.LEGS, new LegModel());
}
else
{
categories.set(AvatarEditorFigureCategory.TORSO, new TorsoModel());
categories.set(AvatarEditorFigureCategory.LEGS, new LegModel());
}
setCategories(categories); setCategories(categories);
}, []); }, [ genderFootballGate ]);
const setupFigures = useCallback(() => const setupFigures = useCallback(() =>
{ {
@ -135,11 +154,12 @@ export const AvatarEditorView: FC<{}> = props =>
resetCategories(); resetCategories();
return; return;
case AvatarEditorAction.ACTION_SAVE: case AvatarEditorAction.ACTION_SAVE:
SendMessageComposer(new UserFigureComposer(figureData.gender, figureData.getFigureString())); !genderFootballGate ? SendMessageComposer(new UserFigureComposer(figureData.gender, figureData.getFigureString())) : SendMessageComposer(new SetClothingChangeDataMessageComposer(objectFootballGate, genderFootballGate, figureData.getFigureString()));
setIsVisible(false); SetLocalStorage(`nitro.look.footballgate.${ genderFootballGate }`, figureData.getFigureString());
onClose();
return; return;
} }
}, [ figureData, lastFigure, lastGender, figureSetIds, loadAvatarInEditor, resetCategories ]) }, [ loadAvatarInEditor, figureData, resetCategories, lastFigure, lastGender, figureSetIds, genderFootballGate, objectFootballGate ])
const setGender = useCallback((gender: string) => const setGender = useCallback((gender: string) =>
{ {
@ -155,6 +175,9 @@ export const AvatarEditorView: FC<{}> = props =>
{ {
const parts = url.split('/'); const parts = url.split('/');
setGenderFootballGate(parts[2] ? parts[2] : null);
setObjectFootballGate(parts[3] ? Number(parts[3]) : null);
if(parts.length < 2) return; if(parts.length < 2) return;
switch(parts[1]) switch(parts[1])
@ -185,25 +208,15 @@ export const AvatarEditorView: FC<{}> = props =>
useEffect(() => useEffect(() =>
{ {
if(!isWardrobeVisible) return;
setActiveCategory(null);
SendMessageComposer(new GetWardrobeMessageComposer()); SendMessageComposer(new GetWardrobeMessageComposer());
}, [ isWardrobeVisible ]); }, []);
useEffect(() =>
{
if(!activeCategory) return;
setIsWardrobeVisible(false);
}, [ activeCategory ]);
useEffect(() => useEffect(() =>
{ {
if(!categories) return; if(!categories) return;
selectCategory(AvatarEditorFigureCategory.GENERIC); selectCategory(!genderFootballGate ? AvatarEditorFigureCategory.GENERIC : AvatarEditorFigureCategory.TORSO);
}, [ categories, selectCategory ]); }, [ categories, genderFootballGate, selectCategory ]);
useEffect(() => useEffect(() =>
{ {
@ -248,9 +261,22 @@ export const AvatarEditorView: FC<{}> = props =>
{ {
if(!isVisible || !isInitalized || !needsReset) return; if(!isVisible || !isInitalized || !needsReset) return;
loadAvatarInEditor(GetSessionDataManager().figure, GetSessionDataManager().gender); if (!genderFootballGate) loadAvatarInEditor(GetSessionDataManager().figure, GetSessionDataManager().gender);
if (genderFootballGate) loadAvatarInEditor(genderFootballGate === FigureData.MALE ? DEFAULT_MALE_FOOTBALL_GATE : DEFAULT_FEMALE_FOOTBALL_GATE, genderFootballGate);
setNeedsReset(false); setNeedsReset(false);
}, [ isVisible, isInitalized, needsReset, loadAvatarInEditor ]); }, [ isVisible, isInitalized, needsReset, loadAvatarInEditor, genderFootballGate, DEFAULT_MALE_FOOTBALL_GATE, DEFAULT_FEMALE_FOOTBALL_GATE ]);
useEffect(() => // This is so when you have the look editor open and you change the mode to Boy or Girl
{
if(!isVisible) return;
return () =>
{
setupFigures();
setIsWardrobeVisible(false);
setNeedsReset(true);
}
}, [ isVisible, genderFootballGate, setupFigures ]);
useEffect(() => useEffect(() =>
{ {
@ -264,36 +290,42 @@ export const AvatarEditorView: FC<{}> = props =>
if(!isVisible || !figureData) return null; if(!isVisible || !figureData) return null;
const avatarEditorClasses = `nitro-avatar-editor no-resize ${ isWardrobeVisible ? 'expanded' : '' }`;
return ( return (
<NitroCardView uniqueKey="avatar-editor" className="nitro-avatar-editor"> <NitroCardView uniqueKey="avatar-editor" className={ avatarEditorClasses }>
<NitroCardHeaderView headerText={ LocalizeText('avatareditor.title') } onCloseClick={ event => setIsVisible(false) } /> <NitroCardHeaderView headerText={ !genderFootballGate ? LocalizeText('avatareditor.title') : LocalizeText('widget.furni.clothingchange.editor.title') } onCloseClick={ onClose } />
<NitroCardTabsView> <NitroCardTabsView className="avatar-editor-tabs">
{ categories && (categories.size > 0) && Array.from(categories.keys()).map(category => { categories && (categories.size > 0) && Array.from(categories.keys()).map(category =>
{ {
const isActive = (activeCategory && (activeCategory.name === category)); const isActive = (activeCategory && (activeCategory.name === category));
return ( return (
<NitroCardTabsItemView key={ category } isActive={ isActive } onClick={ event => selectCategory(category) }> <NitroCardTabsItemView key={ category } isActive={ isActive } onClick={ event => selectCategory(category) }>
{ LocalizeText(`avatareditor.category.${ category }`) } <div className={ `tab ${ category }` }></div>
</NitroCardTabsItemView> </NitroCardTabsItemView>
); );
}) } }) }
<NitroCardTabsItemView isActive={ isWardrobeVisible } onClick={ event => setIsWardrobeVisible(true) }> { (!genderFootballGate) &&
{ LocalizeText('avatareditor.category.wardrobe') } <NitroCardTabsItemView onClick={ event => setIsWardrobeVisible(!isWardrobeVisible) }>
<div className="tab-wardrobe"></div>
</NitroCardTabsItemView> </NitroCardTabsItemView>
}
</NitroCardTabsView> </NitroCardTabsView>
<NitroCardContentView> <NitroCardContentView>
<Grid> <Grid>
<Column size={ 9 } overflow="hidden"> <Column size={ isWardrobeVisible ? 6 : 8 } overflow="hidden">
{ (activeCategory && !isWardrobeVisible) && { (activeCategory) &&
<AvatarEditorModelView model={ activeCategory } gender={ figureData.gender } setGender={ setGender } /> } <AvatarEditorModelView model={ activeCategory } gender={ figureData.gender } setGender={ setGender } />
{ isWardrobeVisible && }
<AvatarEditorWardrobeView figureData={ figureData } savedFigures={ savedFigures } setSavedFigures={ setSavedFigures } loadAvatarInEditor={ loadAvatarInEditor } /> }
</Column> </Column>
<Column size={ 3 } overflow="hidden"> <Column size={ isWardrobeVisible ? 6 : 4 } overflow="hidden">
<Flex gap={ 2 } className="w-100 h-100">
<Flex column={ true } className="w-100">
<AvatarEditorFigurePreviewView figureData={ figureData } /> <AvatarEditorFigurePreviewView figureData={ figureData } />
<Column grow gap={ 1 }> <Column grow gap={ 1 }>
<ButtonGroup> { (!genderFootballGate) &&
<ButtonGroup className="action-buttons w-100">
<Button variant="secondary" onClick={ event => processAction(AvatarEditorAction.ACTION_RESET) }> <Button variant="secondary" onClick={ event => processAction(AvatarEditorAction.ACTION_RESET) }>
<FaUndo className="fa-icon" /> <FaUndo className="fa-icon" />
</Button> </Button>
@ -304,10 +336,18 @@ export const AvatarEditorView: FC<{}> = props =>
<FaDice className="fa-icon" /> <FaDice className="fa-icon" />
</Button> </Button>
</ButtonGroup> </ButtonGroup>
<Button className="w-100" variant="success" onClick={ event => processAction(AvatarEditorAction.ACTION_SAVE) }> }
<Button className="w-10" variant="success" onClick={ event => processAction(AvatarEditorAction.ACTION_SAVE) }>
{ LocalizeText('avatareditor.save') } { LocalizeText('avatareditor.save') }
</Button> </Button>
</Column> </Column>
</Flex>
{ isWardrobeVisible &&
<Column overflow="hidden" className="w-100">
<AvatarEditorWardrobeView figureData={ figureData } savedFigures={ savedFigures } setSavedFigures={ setSavedFigures } loadAvatarInEditor={ loadAvatarInEditor } />
</Column>
}
</Flex>
</Column> </Column>
</Grid> </Grid>
</NitroCardContentView> </NitroCardContentView>

View File

@ -44,11 +44,9 @@ export const AvatarEditorFigurePreviewView: FC<AvatarEditorFigurePreviewViewProp
return ( return (
<Column className="figure-preview-container" overflow="hidden" position="relative"> <Column className="figure-preview-container" overflow="hidden" position="relative">
<LayoutAvatarImageView figure={ figureData.getFigureString() } direction={ figureData.direction } scale={ 2 } /> <LayoutAvatarImageView figure={ figureData.getFigureString() } direction={ figureData.direction } scale={ 2 } />
<AvatarEditorIcon className="avatar-spotlight" icon="spotlight" />
<Base className="avatar-shadow" /> <Base className="avatar-shadow" />
<Base className="arrow-container"> <Base className="arrow-container">
<AvatarEditorIcon pointer icon="arrow-left" onClick={ event => rotateFigure(figureData.direction + 1) } /> <i className="icon arrow-left" onClick={ event => rotateFigure(figureData.direction + 1) } />
<AvatarEditorIcon pointer icon="arrow-right" onClick={ event => rotateFigure(figureData.direction - 1) } />
</Base> </Base>
</Column> </Column>
); );

View File

@ -1,9 +1,12 @@
import { Dispatch, FC, SetStateAction, useCallback, useEffect, useState } from 'react'; import { Dispatch, FC, SetStateAction, useCallback, useEffect, useState } from 'react';
import { CategoryData, FigureData, IAvatarEditorCategoryModel } from '../../../api'; import { CategoryData, FigureData, IAvatarEditorCategoryModel, LocalizeText } from '../../../api';
import { Column, Flex, Grid } from '../../../common'; import { Column, Flex, Grid, Text } from '../../../common';
import { AvatarEditorIcon } from './AvatarEditorIcon'; import { AvatarEditorIcon } from './AvatarEditorIcon';
import { AvatarEditorFigureSetView } from './figure-set/AvatarEditorFigureSetView'; import { AvatarEditorFigureSetView } from './figure-set/AvatarEditorFigureSetView';
import { AvatarEditorPaletteSetView } from './palette-set/AvatarEditorPaletteSetView'; import { AvatarEditorPaletteSetView } from './palette-set/AvatarEditorPaletteSetView';
const CATEGORY_FOOTBALL_GATE = [ 'ch', 'cp', 'lg', 'sh' ];
export interface AvatarEditorModelViewProps export interface AvatarEditorModelViewProps
{ {
model: IAvatarEditorCategoryModel; model: IAvatarEditorCategoryModel;
@ -13,7 +16,7 @@ export interface AvatarEditorModelViewProps
export const AvatarEditorModelView: FC<AvatarEditorModelViewProps> = props => export const AvatarEditorModelView: FC<AvatarEditorModelViewProps> = props =>
{ {
const { model = null, gender = null, setGender = null } = props; const { model = null, gender = null, isFromFootballGate = false, setGender = null } = props;
const [ activeCategory, setActiveCategory ] = useState<CategoryData>(null); const [ activeCategory, setActiveCategory ] = useState<CategoryData>(null);
const [ maxPaletteCount, setMaxPaletteCount ] = useState(1); const [ maxPaletteCount, setMaxPaletteCount ] = useState(1);
@ -53,14 +56,17 @@ export const AvatarEditorModelView: FC<AvatarEditorModelViewProps> = props =>
return ( return (
<Grid> <Grid>
<Column size={ 2 }> <Column className="choose-clothing overflow-y-auto overflow-x-hidden">
<Flex className="px-3" gap={ 4 }>
{ model.canSetGender && { model.canSetGender &&
<> <>
<Flex center pointer className="category-item" onClick={ event => setGender(FigureData.MALE) }> <Flex center pointer className="category-item" gap={ 3 } onClick={ event => setGender(FigureData.MALE) }>
<AvatarEditorIcon icon="male" selected={ (gender === FigureData.MALE) } /> <AvatarEditorIcon icon="male" selected={ (gender === FigureData.MALE) } />
<Text bold>{ LocalizeText('avatareditor.generic.boy') }</Text>
</Flex> </Flex>
<Flex center pointer className="category-item" onClick={ event => setGender(FigureData.FEMALE) }> <Flex center pointer className="category-item" gap={ 3 } onClick={ event => setGender(FigureData.FEMALE) }>
<AvatarEditorIcon icon="female" selected={ (gender === FigureData.FEMALE) } /> <AvatarEditorIcon icon="female" selected={ (gender === FigureData.FEMALE) } />
<Text bold>{ LocalizeText('avatareditor.generic.girl') }</Text>
</Flex> </Flex>
</> } </> }
{ !model.canSetGender && model.categories && (model.categories.size > 0) && Array.from(model.categories.keys()).map(name => { !model.canSetGender && model.categories && (model.categories.size > 0) && Array.from(model.categories.keys()).map(name =>
@ -68,21 +74,31 @@ export const AvatarEditorModelView: FC<AvatarEditorModelViewProps> = props =>
const category = model.categories.get(name); const category = model.categories.get(name);
return ( return (
<Flex center pointer key={ name } className="category-item" onClick={ event => selectCategory(name) }> <div key={ name }>
<Flex center pointer className="category-item" onClick={ event => selectCategory(name) }>
{ (isFromFootballGate && CATEGORY_FOOTBALL_GATE.includes(category.name)) &&
<AvatarEditorIcon icon={ category.name } selected={ (activeCategory === category) } /> <AvatarEditorIcon icon={ category.name } selected={ (activeCategory === category) } />
}
{ (!isFromFootballGate) &&
<AvatarEditorIcon icon={ category.name } selected={ (activeCategory === category) } />
}
</Flex> </Flex>
</div>
); );
}) } }) }
</Flex>
<Column className="avatar-parts-container" size={ 5 } overflow="hidden">
<AvatarEditorFigureSetView model={ model } category={ activeCategory } isFromFootballGate={ isFromFootballGate } setMaxPaletteCount={ setMaxPaletteCount } />
</Column> </Column>
<Column size={ 5 } overflow="hidden"> <Column overflow="hidden" className={
<AvatarEditorFigureSetView model={ model } category={ activeCategory } setMaxPaletteCount={ setMaxPaletteCount } /> maxPaletteCount === 2 ? 'avatar-color-palette-container dual-palette' : 'avatar-color-palette-container'
</Column> }>
<Column size={ 5 } overflow="hidden">
{ (maxPaletteCount >= 1) && { (maxPaletteCount >= 1) &&
<AvatarEditorPaletteSetView model={ model } category={ activeCategory } paletteSet={ activeCategory.getPalette(0) } paletteIndex={ 0 } /> } <AvatarEditorPaletteSetView model={ model } category={ activeCategory } paletteSet={ activeCategory.getPalette(0) } paletteIndex={ 0 } /> }
{ (maxPaletteCount === 2) && { (maxPaletteCount === 2) &&
<AvatarEditorPaletteSetView model={ model } category={ activeCategory } paletteSet={ activeCategory.getPalette(1) } paletteIndex={ 1 } /> } <AvatarEditorPaletteSetView model={ model } category={ activeCategory } paletteSet={ activeCategory.getPalette(1) } paletteIndex={ 1 } /> }
</Column> </Column>
</Column>
</Grid> </Grid>
); );
} }

View File

@ -1,8 +1,8 @@
import { IAvatarFigureContainer, SaveWardrobeOutfitMessageComposer } from '@nitrots/nitro-renderer'; import { HabboClubLevelEnum, IAvatarFigureContainer, SaveWardrobeOutfitMessageComposer } from '@nitrots/nitro-renderer';
import { Dispatch, FC, SetStateAction, useCallback, useMemo } from 'react'; import { Dispatch, FC, SetStateAction, useCallback, useMemo } from 'react';
import { FigureData, GetAvatarRenderManager, GetClubMemberLevel, GetConfiguration, LocalizeText, SendMessageComposer } from '../../../api'; import { MdKeyboardArrowLeft, MdKeyboardArrowRight } from 'react-icons/md';
import { AutoGrid, Base, Button, Flex, LayoutAvatarImageView, LayoutCurrencyIcon, LayoutGridItem } from '../../../common'; import { CreateLinkEvent, FigureData, GetAvatarRenderManager, GetClubMemberLevel, GetConfiguration, GetSessionDataManager, LocalizeText, SendMessageComposer } from '../../../api';
import { Flex, LayoutAvatarImageView, LayoutCurrencyIcon } from '../../../common';
export interface AvatarEditorWardrobeViewProps export interface AvatarEditorWardrobeViewProps
{ {
figureData: FigureData; figureData: FigureData;
@ -30,6 +30,8 @@ export const AvatarEditorWardrobeView: FC<AvatarEditorWardrobeViewProps> = props
{ {
if(!figureData || (index >= savedFigures.length) || (index < 0)) return; if(!figureData || (index >= savedFigures.length) || (index < 0)) return;
if (GetSessionDataManager().clubLevel === HabboClubLevelEnum.NO_CLUB) return CreateLinkEvent('habboUI/open/hccenter');
const newFigures = [ ...savedFigures ]; const newFigures = [ ...savedFigures ];
const figure = figureData.getFigureString(); const figure = figureData.getFigureString();
@ -41,6 +43,22 @@ export const AvatarEditorWardrobeView: FC<AvatarEditorWardrobeViewProps> = props
SendMessageComposer(new SaveWardrobeOutfitMessageComposer((index + 1), figure, gender)); SendMessageComposer(new SaveWardrobeOutfitMessageComposer((index + 1), figure, gender));
}, [ figureData, savedFigures, setSavedFigures ]); }, [ figureData, savedFigures, setSavedFigures ]);
const getClubLevel = useCallback(() =>
{
let highestClubLevel = 0;
savedFigures.forEach(([ figureContainer, gender ]) =>
{
if (figureContainer)
{
const clubLevel = GetAvatarRenderManager().getFigureClubLevel(figureContainer, gender);
highestClubLevel = Math.max(highestClubLevel, clubLevel);
}
});
return highestClubLevel;
}, [ savedFigures ]);
const figures = useMemo(() => const figures = useMemo(() =>
{ {
if(!savedFigures || !savedFigures.length) return []; if(!savedFigures || !savedFigures.length) return [];
@ -54,26 +72,52 @@ export const AvatarEditorWardrobeView: FC<AvatarEditorWardrobeViewProps> = props
if(figureContainer) clubLevel = GetAvatarRenderManager().getFigureClubLevel(figureContainer, gender); if(figureContainer) clubLevel = GetAvatarRenderManager().getFigureClubLevel(figureContainer, gender);
items.push( items.push(
<LayoutGridItem key={ index } position="relative" overflow="hidden" className="nitro-avatar-editor-wardrobe-figure-preview"> <Flex key={ index } alignItems={ 'center' } justifyContent={ 'center' }>
{ figureContainer && <Flex gap={ 1 } column={ true } className="button-container">
<LayoutAvatarImageView figure={ figureContainer.getFigureString() } gender={ gender } direction={ 2 } /> } <button
<Base className="avatar-shadow" /> className="saved-outfit-button"
{ !hcDisabled && (clubLevel > 0) && <LayoutCurrencyIcon className="position-absolute top-1 start-1" type="hc" /> } onClick={ event => saveFigureAtWardrobeIndex(index) }
<Flex gap={ 1 } className="button-container"> disabled={ clubLevel > GetClubMemberLevel() && !hcDisabled }>
<Button variant="link" fullWidth onClick={ event => saveFigureAtWardrobeIndex(index) }>{ LocalizeText('avatareditor.wardrobe.save') }</Button> <MdKeyboardArrowRight />
{ figureContainer && </button>
<Button variant="link" fullWidth onClick={ event => wearFigureAtIndex(index) } disabled={ (clubLevel > GetClubMemberLevel()) }>{ LocalizeText('generic_usable.button.use') }</Button> } { figureContainer && (
<button
className="saved-outfit-button"
onClick={ event => wearFigureAtIndex(index) }
disabled={ clubLevel > GetClubMemberLevel() && !hcDisabled }
>
<MdKeyboardArrowLeft />
</button>
) }
</Flex>
<div className="avatar-container">
{ figureContainer && (
<LayoutAvatarImageView className="avatar-figure" figure={ figureContainer.getFigureString() } gender={ gender } direction={ 4 } />
) }
</div>
</Flex> </Flex>
</LayoutGridItem>
); );
}); });
return items; return items;
}, [ savedFigures, hcDisabled, saveFigureAtWardrobeIndex, wearFigureAtIndex ]); }, [ savedFigures, saveFigureAtWardrobeIndex, wearFigureAtIndex ]);
return ( return (
<AutoGrid columnCount={ 5 } columnMinWidth={ 80 } columnMinHeight={ 140 }> <div>
{ figures } <div className="d-flex flex-column align-items-center">
</AutoGrid> <span className="saved-outfits-title">
{ LocalizeText('avatareditor.wardrobe.title') }
</span>
<span className="mt-2">
{ !hcDisabled && getClubLevel() > 0 && (
<LayoutCurrencyIcon type="hc" />
) }
</span>
</div>
<div className="saved-outfit-container mt-2">
<div className="nitro-avatar-editor-wardrobe-container">{ figures }</div>
</div>
</div>
); );
} }

View File

@ -25,11 +25,13 @@ export const AvatarEditorFigureSetItemView: FC<AvatarEditorFigureSetItemViewProp
}, [ partItem ]); }, [ partItem ]);
return ( return (
<LayoutGridItem itemImage={ (partItem.isClear ? undefined : partItem.imageUrl) } itemActive={ partItem.isSelected } { ...rest }> <div className="avatar-container">
{ !hcDisabled && partItem.isHC && <LayoutCurrencyIcon className="position-absolute end-1 bottom-1" type="hc" /> } <LayoutGridItem className={ `avatar-parts ${ partItem.isSelected ? 'part-selected' : '' }` } itemImage={ (partItem.isClear ? undefined : partItem.imageUrl) } { ...rest }>
{ !hcDisabled && partItem.isHC && <i className="icon hc-icon position-absolute" /> }
{ partItem.isClear && <AvatarEditorIcon icon="clear" /> } { partItem.isClear && <AvatarEditorIcon icon="clear" /> }
{ partItem.isSellable && <AvatarEditorIcon icon="sellable" position="absolute" className="end-1 bottom-1" /> } { partItem.isSellable && <AvatarEditorIcon icon="sellable" position="absolute" className="end-1 bottom-1" /> }
{ children } { children }
</LayoutGridItem> </LayoutGridItem>
</div>
); );
} }

View File

@ -1,8 +1,14 @@
import { HabboClubLevelEnum } from '@nitrots/nitro-renderer';
import { Dispatch, FC, SetStateAction, useCallback, useEffect, useRef } from 'react'; import { Dispatch, FC, SetStateAction, useCallback, useEffect, useRef } from 'react';
import { AvatarEditorGridPartItem, CategoryData, IAvatarEditorCategoryModel } from '../../../../api'; import { AvatarEditorGridPartItem, CategoryData, CreateLinkEvent, GetSessionDataManager, IAvatarEditorCategoryModel } from '../../../../api';
import { AutoGrid } from '../../../../common'; import { AutoGrid } from '../../../../common';
import { AvatarEditorFigureSetItemView } from './AvatarEditorFigureSetItemView'; import { AvatarEditorFigureSetItemView } from './AvatarEditorFigureSetItemView';
const TSHIRT_FOOTBALL_GATE = [ 3111, 3110, 3109, 3030, 3114, 266, 265, 262, 3113, 3112, 691, 690, 667 ];
const NUMBER_BEHIND_FOOTBALL_GATE = [ 3128, 3127, 3126, 3125, 3124, 3123, 3122, 3121, 3120, 3119 ];
const PANTS_FOOTBALL_GATE = [ 3116, 281, 275, 715, 700, 696, 3006 ];
const SHOES_FOOTBALL_GATE = [ 3115, 3068, 906 ];
export interface AvatarEditorFigureSetViewProps export interface AvatarEditorFigureSetViewProps
{ {
model: IAvatarEditorCategoryModel; model: IAvatarEditorCategoryModel;
@ -12,7 +18,7 @@ export interface AvatarEditorFigureSetViewProps
export const AvatarEditorFigureSetView: FC<AvatarEditorFigureSetViewProps> = props => export const AvatarEditorFigureSetView: FC<AvatarEditorFigureSetViewProps> = props =>
{ {
const { model = null, category = null, setMaxPaletteCount = null } = props; const { model = null, category = null, isFromFootballGate = false, setMaxPaletteCount = null } = props;
const elementRef = useRef<HTMLDivElement>(null); const elementRef = useRef<HTMLDivElement>(null);
const selectPart = useCallback((item: AvatarEditorGridPartItem) => const selectPart = useCallback((item: AvatarEditorGridPartItem) =>
@ -21,6 +27,8 @@ export const AvatarEditorFigureSetView: FC<AvatarEditorFigureSetViewProps> = pro
if(index === -1) return; if(index === -1) return;
if (item.isHC && GetSessionDataManager().clubLevel === HabboClubLevelEnum.NO_CLUB) return CreateLinkEvent('habboUI/open/hccenter');
model.selectPart(category.name, index); model.selectPart(category.name, index);
const partItem = category.getCurrentPart(); const partItem = category.getCurrentPart();
@ -36,9 +44,11 @@ export const AvatarEditorFigureSetView: FC<AvatarEditorFigureSetViewProps> = pro
}, [ model, category ]); }, [ model, category ]);
return ( return (
<AutoGrid innerRef={ elementRef } columnCount={ 3 } columnMinHeight={ 50 }> <AutoGrid className="clothing-container" innerRef={ elementRef } columnCount={ 3 } columnMinHeight={ 50 }>
{ (category.parts.length > 0) && category.parts.map((item, index) => { (category.parts.length > 0) && category.parts.map(item =>
<AvatarEditorFigureSetItemView key={ index } partItem={ item } onClick={ event => selectPart(item) } />) } (!isFromFootballGate || (isFromFootballGate && TSHIRT_FOOTBALL_GATE.includes(item.id) || NUMBER_BEHIND_FOOTBALL_GATE.includes(item.id) || PANTS_FOOTBALL_GATE.includes(item.id) || SHOES_FOOTBALL_GATE.includes(item.id))) &&
<AvatarEditorFigureSetItemView key={ item.id } partItem={ item } onClick={ event => selectPart(item) } />)
}
</AutoGrid> </AutoGrid>
); );
} }

View File

@ -1,6 +1,7 @@
import { FC, useEffect, useState } from 'react'; import { FC, useEffect, useState } from 'react';
import { AvatarEditorGridColorItem, GetConfiguration } from '../../../../api'; import { AvatarEditorGridColorItem, GetConfiguration } from '../../../../api';
import { LayoutCurrencyIcon, LayoutGridItem, LayoutGridItemProps } from '../../../../common'; import { LayoutCurrencyIcon, LayoutGridItem, LayoutGridItemProps } from '../../../../common';
import { LayoutGridColorPickerItem } from '../../../../common/layout/LayoutGridColorPickerItem';
export interface AvatarEditorPaletteSetItemProps extends LayoutGridItemProps export interface AvatarEditorPaletteSetItemProps extends LayoutGridItemProps
{ {
@ -24,9 +25,9 @@ export const AvatarEditorPaletteSetItem: FC<AvatarEditorPaletteSetItemProps> = p
}, [ colorItem ]); }, [ colorItem ]);
return ( return (
<LayoutGridItem itemHighlight itemColor={ colorItem.color } itemActive={ colorItem.isSelected } className="clear-bg" { ...rest }> <LayoutGridColorPickerItem itemHighlight itemColor={ colorItem.color } itemActive={ colorItem.isSelected } className="color-picker-frame clear-bg" { ...rest }>
{ !hcDisabled && colorItem.isHC && <LayoutCurrencyIcon className="position-absolute end-1 bottom-1" type="hc" /> } { !hcDisabled && colorItem.isHC && <i className="icon hc-icon position-absolute" /> }
{ children } { children }
</LayoutGridItem> </LayoutGridColorPickerItem>
); );
} }

View File

@ -1,5 +1,6 @@
import { HabboClubLevelEnum } from '@nitrots/nitro-renderer';
import { FC, useCallback, useEffect, useRef } from 'react'; import { FC, useCallback, useEffect, useRef } from 'react';
import { AvatarEditorGridColorItem, CategoryData, IAvatarEditorCategoryModel } from '../../../../api'; import { AvatarEditorGridColorItem, CategoryData, CreateLinkEvent, GetSessionDataManager, IAvatarEditorCategoryModel } from '../../../../api';
import { AutoGrid } from '../../../../common'; import { AutoGrid } from '../../../../common';
import { AvatarEditorPaletteSetItem } from './AvatarEditorPaletteSetItemView'; import { AvatarEditorPaletteSetItem } from './AvatarEditorPaletteSetItemView';
@ -22,6 +23,8 @@ export const AvatarEditorPaletteSetView: FC<AvatarEditorPaletteSetViewProps> = p
if(index === -1) return; if(index === -1) return;
if (item.isHC && GetSessionDataManager().clubLevel === HabboClubLevelEnum.NO_CLUB) return CreateLinkEvent('habboUI/open/hccenter');
model.selectColor(category.name, index, paletteIndex); model.selectColor(category.name, index, paletteIndex);
}, [ model, category, paletteSet, paletteIndex ]); }, [ model, category, paletteSet, paletteIndex ]);
@ -33,9 +36,21 @@ export const AvatarEditorPaletteSetView: FC<AvatarEditorPaletteSetViewProps> = p
}, [ model, category ]); }, [ model, category ]);
return ( return (
<AutoGrid innerRef={ elementRef } gap={ 1 } columnCount={ 5 } columnMinWidth={ 30 }> <AutoGrid
{ (paletteSet.length > 0) && paletteSet.map((item, index) => className="py-1 avatar-editor-palette-set-view"
<AvatarEditorPaletteSetItem key={ index } colorItem={ item } onClick={ event => selectColor(item) } />) } innerRef={ elementRef }
gap={ 1 }
columnCount={ 8 }
columnMinWidth={ 14 }
>
{ paletteSet.length > 0 &&
paletteSet.map((item, index) => (
<AvatarEditorPaletteSetItem
key={ index }
colorItem={ item }
onClick={ (event) => selectColor(item) }
/>
)) }
</AutoGrid> </AutoGrid>
); );
} }