Fix: Avatar Editor
@ -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;
|
||||||
|
BIN
src/assets/images/avatareditor/avatar_shadow.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
src/assets/images/avatareditor/body.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
src/assets/images/avatareditor/choice_bg.png
Normal file
After Width: | Height: | Size: 2.9 KiB |
BIN
src/assets/images/avatareditor/color_frame.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
src/assets/images/avatareditor/color_frame_active.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
src/assets/images/avatareditor/hc_icon.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
src/assets/images/avatareditor/head.png
Normal file
After Width: | Height: | Size: 2.8 KiB |
BIN
src/assets/images/avatareditor/legs.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
src/assets/images/avatareditor/randomize.png
Normal file
After Width: | Height: | Size: 2.8 KiB |
BIN
src/assets/images/avatareditor/randomize_transparent.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
src/assets/images/avatareditor/rotation_arrow.png
Normal file
After Width: | Height: | Size: 2.8 KiB |
BIN
src/assets/images/avatareditor/shirts.png
Normal file
After Width: | Height: | Size: 2.5 KiB |
BIN
src/assets/images/avatareditor/wardrobe_bg.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
src/assets/images/avatareditor/wardrobe_left.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
src/assets/images/avatareditor/wardrobe_right.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
src/assets/images/avatareditor/wardrobe_user_bg.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
src/assets/images/wardrobe/hd.png
Normal file
After Width: | Height: | Size: 225 B |
BIN
src/assets/images/wardrobe/head.png
Normal file
After Width: | Height: | Size: 330 B |
BIN
src/assets/images/wardrobe/legs.png
Normal file
After Width: | Height: | Size: 231 B |
BIN
src/assets/images/wardrobe/torso.png
Normal file
After Width: | Height: | Size: 247 B |
BIN
src/assets/images/wardrobe/wardrobe.png
Normal file
After Width: | Height: | Size: 256 B |
@ -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;
|
||||||
|
75
src/common/layout/LayoutGridColorPickerItem.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
@ -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;
|
z-index: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:after {
|
|
||||||
position: absolute;
|
|
||||||
content: '';
|
|
||||||
top: 75%;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
border-radius: 50%;
|
|
||||||
background-color: #a68d76;
|
|
||||||
box-shadow: 0 0 8px 2px rgba($white,.6);
|
|
||||||
transform: scale(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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.randomize-icon {
|
||||||
|
background-image: url(@/assets/images/avatareditor/randomize_transparent.png);
|
||||||
|
height: 33px;
|
||||||
|
width: 39px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-image: url(@/assets/images/avatareditor/randomize.png);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|