mirror of
https://github.com/billsonnn/nitro-react.git
synced 2025-02-18 18:02:36 +01:00
Begin new avatar editor
This commit is contained in:
parent
382cb665d8
commit
d675258adb
@ -10,7 +10,7 @@
|
|||||||
"eslint": "eslint src --ext .ts,.tsx"
|
"eslint": "eslint src --ext .ts,.tsx"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tanstack/react-virtual": "3.0.0-alpha.0",
|
"@tanstack/react-virtual": "3.2.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-bootstrap": "^2.2.2",
|
"react-bootstrap": "^2.2.2",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
|
152
src/api/avatar/AvatarEditorThumbnailsHelper.ts
Normal file
152
src/api/avatar/AvatarEditorThumbnailsHelper.ts
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
import { AvatarFigurePartType, GetAssetManager, GetAvatarRenderManager, IFigurePart, IGraphicAsset, IPartColor, NitroAlphaFilter, NitroContainer, NitroSprite, TextureUtils } from '@nitrots/nitro-renderer';
|
||||||
|
import { FigureData } from './FigureData';
|
||||||
|
import { IAvatarEditorCategoryPartItem } from './IAvatarEditorCategoryPartItem';
|
||||||
|
|
||||||
|
export class AvatarEditorThumbnailsHelper
|
||||||
|
{
|
||||||
|
private static THUMBNAIL_CACHE: Map<string, string> = new Map();
|
||||||
|
private static THUMB_DIRECTIONS: number[] = [ 2, 6, 0, 4, 3, 1 ];
|
||||||
|
private static ALPHA_FILTER: NitroAlphaFilter = new NitroAlphaFilter({ alpha: 0.2 });
|
||||||
|
private static DRAW_ORDER: string[] = [
|
||||||
|
AvatarFigurePartType.LEFT_HAND_ITEM,
|
||||||
|
AvatarFigurePartType.LEFT_HAND,
|
||||||
|
AvatarFigurePartType.LEFT_SLEEVE,
|
||||||
|
AvatarFigurePartType.LEFT_COAT_SLEEVE,
|
||||||
|
AvatarFigurePartType.BODY,
|
||||||
|
AvatarFigurePartType.SHOES,
|
||||||
|
AvatarFigurePartType.LEGS,
|
||||||
|
AvatarFigurePartType.CHEST,
|
||||||
|
AvatarFigurePartType.CHEST_ACCESSORY,
|
||||||
|
AvatarFigurePartType.COAT_CHEST,
|
||||||
|
AvatarFigurePartType.CHEST_PRINT,
|
||||||
|
AvatarFigurePartType.WAIST_ACCESSORY,
|
||||||
|
AvatarFigurePartType.RIGHT_HAND,
|
||||||
|
AvatarFigurePartType.RIGHT_SLEEVE,
|
||||||
|
AvatarFigurePartType.RIGHT_COAT_SLEEVE,
|
||||||
|
AvatarFigurePartType.HEAD,
|
||||||
|
AvatarFigurePartType.FACE,
|
||||||
|
AvatarFigurePartType.EYES,
|
||||||
|
AvatarFigurePartType.HAIR,
|
||||||
|
AvatarFigurePartType.HAIR_BIG,
|
||||||
|
AvatarFigurePartType.FACE_ACCESSORY,
|
||||||
|
AvatarFigurePartType.EYE_ACCESSORY,
|
||||||
|
AvatarFigurePartType.HEAD_ACCESSORY,
|
||||||
|
AvatarFigurePartType.HEAD_ACCESSORY_EXTRA,
|
||||||
|
AvatarFigurePartType.RIGHT_HAND_ITEM,
|
||||||
|
];
|
||||||
|
|
||||||
|
private static getThumbnailKey(setType: string, part: IAvatarEditorCategoryPartItem): string
|
||||||
|
{
|
||||||
|
return `${ setType }-${ part.partSet.id }`;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async build(setType: string, part: IAvatarEditorCategoryPartItem, useColors: boolean, isDisabled: boolean = false): Promise<string>
|
||||||
|
{
|
||||||
|
if(!setType || !setType.length || !part || !part.partSet || !part.partSet.parts || !part.partSet.parts.length) return null;
|
||||||
|
|
||||||
|
const thumbnailKey = this.getThumbnailKey(setType, part);
|
||||||
|
const cached = this.THUMBNAIL_CACHE.get(thumbnailKey);
|
||||||
|
|
||||||
|
if(cached) return cached;
|
||||||
|
|
||||||
|
const buildContainer = (part: IAvatarEditorCategoryPartItem, useColors: boolean, isDisabled: boolean = false) =>
|
||||||
|
{
|
||||||
|
const container = new NitroContainer();
|
||||||
|
const parts = part.partSet.parts.concat().sort(this.sortByDrawOrder);
|
||||||
|
|
||||||
|
for(const part of parts)
|
||||||
|
{
|
||||||
|
if(!part) continue;
|
||||||
|
|
||||||
|
let asset: IGraphicAsset = null;
|
||||||
|
let direction = 0;
|
||||||
|
let hasAsset = false;
|
||||||
|
|
||||||
|
while(!hasAsset && (direction < AvatarEditorThumbnailsHelper.THUMB_DIRECTIONS.length))
|
||||||
|
{
|
||||||
|
const assetName = `${ FigureData.SCALE }_${ FigureData.STD }_${ part.type }_${ part.id }_${ AvatarEditorThumbnailsHelper.THUMB_DIRECTIONS[direction] }_${ FigureData.DEFAULT_FRAME }`;
|
||||||
|
|
||||||
|
asset = GetAssetManager().getAsset(assetName);
|
||||||
|
|
||||||
|
if(asset && asset.texture)
|
||||||
|
{
|
||||||
|
hasAsset = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
direction++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!hasAsset) continue;
|
||||||
|
|
||||||
|
const x = asset.offsetX;
|
||||||
|
const y = asset.offsetY;
|
||||||
|
let partColor: IPartColor = null;
|
||||||
|
|
||||||
|
if(useColors && (part.colorLayerIndex > 0))
|
||||||
|
{
|
||||||
|
//const color = this._partColors[(part.colorLayerIndex - 1)];
|
||||||
|
|
||||||
|
//if(color) partColor = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sprite = new NitroSprite(asset.texture);
|
||||||
|
|
||||||
|
sprite.position.set(x, y);
|
||||||
|
|
||||||
|
if(partColor) sprite.tint = partColor.rgb;
|
||||||
|
|
||||||
|
if(isDisabled) container.filters = [ AvatarEditorThumbnailsHelper.ALPHA_FILTER ];
|
||||||
|
|
||||||
|
container.addChild(sprite);
|
||||||
|
}
|
||||||
|
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise(async (resolve, reject) =>
|
||||||
|
{
|
||||||
|
const resetFigure = async (figure: string) =>
|
||||||
|
{
|
||||||
|
const container = buildContainer(part, useColors, isDisabled);
|
||||||
|
const url = await TextureUtils.generateImageUrl(container);
|
||||||
|
|
||||||
|
AvatarEditorThumbnailsHelper.THUMBNAIL_CACHE.set(thumbnailKey, url);
|
||||||
|
|
||||||
|
resolve(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
const figureContainer = GetAvatarRenderManager().createFigureContainer(`${ setType }-${ part.partSet.id }`);
|
||||||
|
|
||||||
|
if(!GetAvatarRenderManager().isFigureContainerReady(figureContainer))
|
||||||
|
{
|
||||||
|
GetAvatarRenderManager().downloadAvatarFigure(figureContainer, {
|
||||||
|
resetFigure,
|
||||||
|
dispose: null,
|
||||||
|
disposed: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
resetFigure(null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static sortByDrawOrder(a: IFigurePart, b: IFigurePart): number
|
||||||
|
{
|
||||||
|
const indexA = AvatarEditorThumbnailsHelper.DRAW_ORDER.indexOf(a.type);
|
||||||
|
const indexB = AvatarEditorThumbnailsHelper.DRAW_ORDER.indexOf(b.type);
|
||||||
|
|
||||||
|
if(indexA < indexB) return -1;
|
||||||
|
|
||||||
|
if(indexA > indexB) return 1;
|
||||||
|
|
||||||
|
if(a.index < b.index) return -1;
|
||||||
|
|
||||||
|
if(a.index > b.index) return 1;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
9
src/api/avatar/IAvatarEditorCategory.ts
Normal file
9
src/api/avatar/IAvatarEditorCategory.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { IPartColor } from '@nitrots/nitro-renderer';
|
||||||
|
import { IAvatarEditorCategoryPartItem } from './IAvatarEditorCategoryPartItem';
|
||||||
|
|
||||||
|
export interface IAvatarEditorCategory
|
||||||
|
{
|
||||||
|
setType: string;
|
||||||
|
partItems: IAvatarEditorCategoryPartItem[];
|
||||||
|
colorItems: IPartColor[][];
|
||||||
|
}
|
10
src/api/avatar/IAvatarEditorCategoryPartItem.ts
Normal file
10
src/api/avatar/IAvatarEditorCategoryPartItem.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { IFigurePartSet } from '@nitrots/nitro-renderer';
|
||||||
|
|
||||||
|
export interface IAvatarEditorCategoryPartItem
|
||||||
|
{
|
||||||
|
id?: number;
|
||||||
|
partSet?: IFigurePartSet;
|
||||||
|
usesColor?: boolean;
|
||||||
|
maxPaletteCount?: number;
|
||||||
|
isClear?: boolean;
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
export * from './AvatarEditorAction';
|
export * from './AvatarEditorAction';
|
||||||
export * from './AvatarEditorGridColorItem';
|
export * from './AvatarEditorGridColorItem';
|
||||||
export * from './AvatarEditorGridPartItem';
|
export * from './AvatarEditorGridPartItem';
|
||||||
|
export * from './AvatarEditorThumbnailsHelper';
|
||||||
export * from './AvatarEditorUtilities';
|
export * from './AvatarEditorUtilities';
|
||||||
export * from './BodyModel';
|
export * from './BodyModel';
|
||||||
export * from './CategoryBaseModel';
|
export * from './CategoryBaseModel';
|
||||||
@ -8,6 +9,8 @@ export * from './CategoryData';
|
|||||||
export * from './FigureData';
|
export * from './FigureData';
|
||||||
export * from './FigureGenerator';
|
export * from './FigureGenerator';
|
||||||
export * from './HeadModel';
|
export * from './HeadModel';
|
||||||
|
export * from './IAvatarEditorCategory';
|
||||||
export * from './IAvatarEditorCategoryModel';
|
export * from './IAvatarEditorCategoryModel';
|
||||||
|
export * from './IAvatarEditorCategoryPartItem';
|
||||||
export * from './LegModel';
|
export * from './LegModel';
|
||||||
export * from './TorsoModel';
|
export * from './TorsoModel';
|
||||||
|
69
src/common/InfiniteGrid.tsx
Normal file
69
src/common/InfiniteGrid.tsx
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
import { useVirtualizer } from '@tanstack/react-virtual';
|
||||||
|
import { FC, Fragment, ReactElement, useRef } from 'react';
|
||||||
|
import { Base } from './Base';
|
||||||
|
import { Flex } from './Flex';
|
||||||
|
|
||||||
|
interface InfiniteGridProps<T = any>
|
||||||
|
{
|
||||||
|
rows: T[];
|
||||||
|
columnCount: number;
|
||||||
|
overscan?: number;
|
||||||
|
itemRender?: (item: T) => ReactElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const InfiniteGrid: FC<InfiniteGridProps> = props =>
|
||||||
|
{
|
||||||
|
const { rows = [], columnCount = 4, overscan = 5, itemRender = null } = props;
|
||||||
|
const parentRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
const virtualizer = useVirtualizer({
|
||||||
|
count: Math.ceil(rows.length / columnCount),
|
||||||
|
overscan,
|
||||||
|
getScrollElement: () => parentRef.current,
|
||||||
|
estimateSize: () => 45,
|
||||||
|
});
|
||||||
|
const items = virtualizer.getVirtualItems();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Base innerRef={ parentRef } fit position="relative" style={ { overflowY: 'auto' } }>
|
||||||
|
<div
|
||||||
|
style={ {
|
||||||
|
height: virtualizer.getTotalSize(),
|
||||||
|
width: '100%',
|
||||||
|
position: 'relative'
|
||||||
|
} }>
|
||||||
|
<Flex
|
||||||
|
column
|
||||||
|
gap={ 1 }
|
||||||
|
style={ {
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
width: '100%',
|
||||||
|
transform: `translateY(${ items[0]?.start ?? 0 }px)`
|
||||||
|
} }>
|
||||||
|
{ items.map(virtualRow => (
|
||||||
|
<Flex
|
||||||
|
gap={ 1 }
|
||||||
|
key={ virtualRow.key + 'a' }
|
||||||
|
data-index={ virtualRow.index }
|
||||||
|
ref={ virtualizer.measureElement }
|
||||||
|
style={ {
|
||||||
|
minHeight: virtualRow.index === 0 ? 45 : virtualRow.size
|
||||||
|
} }>
|
||||||
|
{ Array.from(Array(columnCount)).map((e,i) =>
|
||||||
|
{
|
||||||
|
const item = rows[i + (virtualRow.index * columnCount)];
|
||||||
|
|
||||||
|
if(!item) return <Fragment
|
||||||
|
key={ virtualRow.index + i + 'b' } />;
|
||||||
|
|
||||||
|
return itemRender(item);
|
||||||
|
}) }
|
||||||
|
</Flex>
|
||||||
|
)) }
|
||||||
|
</Flex>
|
||||||
|
</div>
|
||||||
|
</Base>
|
||||||
|
);
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
import { useVirtual } from '@tanstack/react-virtual';
|
import { useVirtualizer } from '@tanstack/react-virtual';
|
||||||
import { FC, Fragment, ReactElement, useEffect, useRef, useState } from 'react';
|
import { FC, ReactElement, useRef, useState } from 'react';
|
||||||
import { Base } from './Base';
|
import { Base } from './Base';
|
||||||
|
|
||||||
interface InfiniteScrollProps<T = any>
|
interface InfiniteScrollProps<T = any>
|
||||||
@ -14,50 +14,43 @@ export const InfiniteScroll: FC<InfiniteScrollProps> = props =>
|
|||||||
{
|
{
|
||||||
const { rows = [], overscan = 5, scrollToBottom = false, rowRender = null } = props;
|
const { rows = [], overscan = 5, scrollToBottom = false, rowRender = null } = props;
|
||||||
const [ scrollIndex, setScrollIndex ] = useState<number>(rows.length - 1);
|
const [ scrollIndex, setScrollIndex ] = useState<number>(rows.length - 1);
|
||||||
const elementRef = useRef<HTMLDivElement>(null);
|
const parentRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const { virtualItems = [], totalSize = 0, scrollToIndex = null } = useVirtual({
|
|
||||||
parentRef: elementRef,
|
const virtualizer = useVirtualizer({
|
||||||
size: rows.length,
|
count: rows.length,
|
||||||
overscan
|
overscan,
|
||||||
|
getScrollElement: () => parentRef.current,
|
||||||
|
estimateSize: () => 45,
|
||||||
});
|
});
|
||||||
|
const items = virtualizer.getVirtualItems();
|
||||||
const paddingTop = (virtualItems.length > 0) ? (virtualItems?.[0]?.start || 0) : 0
|
|
||||||
const paddingBottom = (virtualItems.length > 0) ? (totalSize - (virtualItems?.[virtualItems.length - 1]?.end || 0)) : 0;
|
|
||||||
|
|
||||||
useEffect(() =>
|
|
||||||
{
|
|
||||||
if(!scrollToBottom) return;
|
|
||||||
|
|
||||||
scrollToIndex(scrollIndex);
|
|
||||||
}, [ scrollToBottom, scrollIndex, scrollToIndex ]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Base fit innerRef={ elementRef } position="relative" overflow="auto">
|
<Base fit innerRef={ parentRef } position="relative" overflow="auto">
|
||||||
{ (paddingTop > 0) &&
|
|
||||||
<div
|
<div
|
||||||
style={ { minHeight: `${ paddingTop }px` } } /> }
|
style={ {
|
||||||
{ virtualItems.map(item =>
|
height: virtualizer.getTotalSize(),
|
||||||
{
|
width: '100%',
|
||||||
const row = rows[item.index];
|
position: 'relative'
|
||||||
|
} }>
|
||||||
if (!row) return (
|
|
||||||
<Fragment
|
|
||||||
key={ item.key } />
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
<div
|
||||||
key={ item.key }
|
style={ {
|
||||||
data-index={ item.index }
|
position: 'absolute',
|
||||||
ref={ item.measureRef }>
|
top: 0,
|
||||||
{ rowRender(row) }
|
left: 0,
|
||||||
|
width: '100%',
|
||||||
|
transform: `translateY(${ items[0]?.start ?? 0 }px)`
|
||||||
|
} }>
|
||||||
|
{ items.map((virtualRow) => (
|
||||||
|
<div
|
||||||
|
key={ virtualRow.key }
|
||||||
|
data-index={ virtualRow.index }
|
||||||
|
ref={ virtualizer.measureElement }>
|
||||||
|
{ rowRender(rows[virtualRow.index]) }
|
||||||
|
</div>
|
||||||
|
)) }
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
|
||||||
}) }
|
|
||||||
{ (paddingBottom > 0) &&
|
|
||||||
<div
|
|
||||||
style={ { minHeight: `${ paddingBottom }px` } } /> }
|
|
||||||
</Base>
|
</Base>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -2,21 +2,22 @@ export * from './AutoGrid';
|
|||||||
export * from './Base';
|
export * from './Base';
|
||||||
export * from './Button';
|
export * from './Button';
|
||||||
export * from './ButtonGroup';
|
export * from './ButtonGroup';
|
||||||
export * from './card';
|
|
||||||
export * from './card/accordion';
|
|
||||||
export * from './card/tabs';
|
|
||||||
export * from './classNames';
|
|
||||||
export * from './Column';
|
export * from './Column';
|
||||||
export * from './draggable-window';
|
|
||||||
export * from './Flex';
|
export * from './Flex';
|
||||||
export * from './FormGroup';
|
export * from './FormGroup';
|
||||||
export * from './Grid';
|
export * from './Grid';
|
||||||
export * from './GridContext';
|
export * from './GridContext';
|
||||||
export * from './HorizontalRule';
|
export * from './HorizontalRule';
|
||||||
|
export * from './InfiniteGrid';
|
||||||
export * from './InfiniteScroll';
|
export * from './InfiniteScroll';
|
||||||
|
export * from './Text';
|
||||||
|
export * from './card';
|
||||||
|
export * from './card/accordion';
|
||||||
|
export * from './card/tabs';
|
||||||
|
export * from './classNames';
|
||||||
|
export * from './draggable-window';
|
||||||
export * from './layout';
|
export * from './layout';
|
||||||
export * from './layout/limited-edition';
|
export * from './layout/limited-edition';
|
||||||
export * from './Text';
|
|
||||||
export * from './transitions';
|
export * from './transitions';
|
||||||
export * from './types';
|
export * from './types';
|
||||||
export * from './utils';
|
export * from './utils';
|
||||||
|
336
src/components/avatar-editor-new/AvatarEditorView.scss
Normal file
336
src/components/avatar-editor-new/AvatarEditorView.scss
Normal file
@ -0,0 +1,336 @@
|
|||||||
|
.nitro-avatar-editor-spritesheet {
|
||||||
|
background: url('@/assets/images/avatareditor/avatar-editor-spritesheet.png') transparent no-repeat;
|
||||||
|
|
||||||
|
&.arrow-left-icon {
|
||||||
|
width: 28px;
|
||||||
|
height: 21px;
|
||||||
|
background-position: -226px -131px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.arrow-right-icon {
|
||||||
|
width: 28px;
|
||||||
|
height: 21px;
|
||||||
|
background-position: -226px -162px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.ca-icon {
|
||||||
|
width: 25px;
|
||||||
|
height: 25px;
|
||||||
|
background-position: -226px -61px;
|
||||||
|
|
||||||
|
&.selected {
|
||||||
|
width: 25px;
|
||||||
|
height: 25px;
|
||||||
|
background-position: -226px -96px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.cc-icon {
|
||||||
|
width: 31px;
|
||||||
|
height: 29px;
|
||||||
|
background-position: -145px -5px;
|
||||||
|
|
||||||
|
&.selected {
|
||||||
|
width: 31px;
|
||||||
|
height: 29px;
|
||||||
|
background-position: -145px -44px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.ch-icon {
|
||||||
|
width: 29px;
|
||||||
|
height: 24px;
|
||||||
|
background-position: -186px -39px;
|
||||||
|
|
||||||
|
&.selected {
|
||||||
|
width: 29px;
|
||||||
|
height: 24px;
|
||||||
|
background-position: -186px -73px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.clear-icon {
|
||||||
|
width: 27px;
|
||||||
|
height: 27px;
|
||||||
|
background-position: -145px -157px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.cp-icon {
|
||||||
|
width: 30px;
|
||||||
|
height: 24px;
|
||||||
|
background-position: -145px -264px;
|
||||||
|
|
||||||
|
&.selected {
|
||||||
|
width: 30px;
|
||||||
|
height: 24px;
|
||||||
|
background-position: -186px -5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
&.ea-icon {
|
||||||
|
width: 35px;
|
||||||
|
height: 16px;
|
||||||
|
background-position: -226px -193px;
|
||||||
|
|
||||||
|
&.selected {
|
||||||
|
width: 35px;
|
||||||
|
height: 16px;
|
||||||
|
background-position: -226px -219px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.fa-icon {
|
||||||
|
width: 27px;
|
||||||
|
height: 20px;
|
||||||
|
background-position: -186px -137px;
|
||||||
|
|
||||||
|
&.selected {
|
||||||
|
width: 27px;
|
||||||
|
height: 20px;
|
||||||
|
background-position: -186px -107px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.female-icon {
|
||||||
|
width: 18px;
|
||||||
|
height: 27px;
|
||||||
|
background-position: -186px -202px;
|
||||||
|
|
||||||
|
&.selected {
|
||||||
|
width: 18px;
|
||||||
|
height: 27px;
|
||||||
|
background-position: -186px -239px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.ha-icon {
|
||||||
|
width: 25px;
|
||||||
|
height: 22px;
|
||||||
|
background-position: -226px -245px;
|
||||||
|
|
||||||
|
&.selected {
|
||||||
|
width: 25px;
|
||||||
|
height: 22px;
|
||||||
|
background-position: -226px -277px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.he-icon {
|
||||||
|
width: 31px;
|
||||||
|
height: 27px;
|
||||||
|
background-position: -145px -83px;
|
||||||
|
|
||||||
|
&.selected {
|
||||||
|
width: 31px;
|
||||||
|
height: 27px;
|
||||||
|
background-position: -145px -120px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.hr-icon {
|
||||||
|
width: 29px;
|
||||||
|
height: 25px;
|
||||||
|
background-position: -145px -194px;
|
||||||
|
|
||||||
|
&.selected {
|
||||||
|
width: 29px;
|
||||||
|
height: 25px;
|
||||||
|
background-position: -145px -229px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.lg-icon {
|
||||||
|
width: 19px;
|
||||||
|
height: 20px;
|
||||||
|
background-position: -303px -45px;
|
||||||
|
|
||||||
|
&.selected {
|
||||||
|
width: 19px;
|
||||||
|
height: 20px;
|
||||||
|
background-position: -303px -75px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.loading-icon {
|
||||||
|
width: 21px;
|
||||||
|
height: 25px;
|
||||||
|
background-position: -186px -167px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
&.male-icon {
|
||||||
|
width: 21px;
|
||||||
|
height: 21px;
|
||||||
|
background-position: -186px -276px;
|
||||||
|
|
||||||
|
&.selected {
|
||||||
|
width: 21px;
|
||||||
|
height: 21px;
|
||||||
|
background-position: -272px -5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
&.sellable-icon {
|
||||||
|
width: 17px;
|
||||||
|
height: 15px;
|
||||||
|
background-position: -303px -105px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
&.sh-icon {
|
||||||
|
width: 37px;
|
||||||
|
height: 10px;
|
||||||
|
background-position: -303px -5px;
|
||||||
|
|
||||||
|
&.selected {
|
||||||
|
width: 37px;
|
||||||
|
height: 10px;
|
||||||
|
background-position: -303px -25px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
&.spotlight-icon {
|
||||||
|
width: 130px;
|
||||||
|
height: 305px;
|
||||||
|
background-position: -5px -5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
&.wa-icon {
|
||||||
|
width: 36px;
|
||||||
|
height: 18px;
|
||||||
|
background-position: -226px -5px;
|
||||||
|
|
||||||
|
&.selected {
|
||||||
|
width: 36px;
|
||||||
|
height: 18px;
|
||||||
|
background-position: -226px -33px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.nitro-avatar-editor-wardrobe-figure-preview {
|
||||||
|
background-color: $pale-sky;
|
||||||
|
overflow: hidden;
|
||||||
|
z-index: 1;
|
||||||
|
|
||||||
|
.avatar-image {
|
||||||
|
position: absolute;
|
||||||
|
bottom: -15px;
|
||||||
|
margin: 0 auto;
|
||||||
|
z-index: 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-shadow {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 25px;
|
||||||
|
width: 40px;
|
||||||
|
height: 20px;
|
||||||
|
margin: 0 auto;
|
||||||
|
border-radius: 100%;
|
||||||
|
background-color: rgba(0, 0, 0, 0.20);
|
||||||
|
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 {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.nitro-avatar-editor {
|
||||||
|
width: $avatar-editor-width;
|
||||||
|
height: $avatar-editor-height;
|
||||||
|
|
||||||
|
.category-item {
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.figure-preview-container {
|
||||||
|
position: relative;
|
||||||
|
height: 100%;
|
||||||
|
background-color: $pale-sky;
|
||||||
|
overflow: hidden;
|
||||||
|
z-index: 1;
|
||||||
|
|
||||||
|
.arrow-container {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0 10px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
bottom: 12px;
|
||||||
|
z-index: 5;
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-image {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 50px;
|
||||||
|
margin: 0 auto;
|
||||||
|
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 {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 15px;
|
||||||
|
width: 70px;
|
||||||
|
height: 30px;
|
||||||
|
margin: 0 auto;
|
||||||
|
border-radius: 100%;
|
||||||
|
background-color: rgba(0, 0, 0, 0.20);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
109
src/components/avatar-editor-new/AvatarEditorView.tsx
Normal file
109
src/components/avatar-editor-new/AvatarEditorView.tsx
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
import { AddLinkEventTracker, AvatarEditorFigureCategory, ILinkEventTracker, RemoveLinkEventTracker } from '@nitrots/nitro-renderer';
|
||||||
|
import { FC, useEffect, useState } from 'react';
|
||||||
|
import { FaDice, FaTrash, FaUndo } from 'react-icons/fa';
|
||||||
|
import { AvatarEditorAction, LocalizeText } from '../../api';
|
||||||
|
import { Button, ButtonGroup, Column, Grid, NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView } from '../../common';
|
||||||
|
import { useAvatarEditor } from '../../hooks';
|
||||||
|
import { AvatarEditorModelView } from './views/AvatarEditorModelView';
|
||||||
|
|
||||||
|
const DEFAULT_MALE_FIGURE: string = 'hr-100.hd-180-7.ch-215-66.lg-270-79.sh-305-62.ha-1002-70.wa-2007';
|
||||||
|
const DEFAULT_FEMALE_FIGURE: string = 'hr-515-33.hd-600-1.ch-635-70.lg-716-66-62.sh-735-68';
|
||||||
|
|
||||||
|
export const AvatarEditorNewView: FC<{}> = props =>
|
||||||
|
{
|
||||||
|
const [ isVisible, setIsVisible ] = useState(false);
|
||||||
|
const { avatarModels, activeModelKey, setActiveModelKey } = useAvatarEditor();
|
||||||
|
|
||||||
|
const processAction = (action: string) =>
|
||||||
|
{
|
||||||
|
switch(action)
|
||||||
|
{
|
||||||
|
case AvatarEditorAction.ACTION_CLEAR:
|
||||||
|
return;
|
||||||
|
case AvatarEditorAction.ACTION_RESET:
|
||||||
|
return;
|
||||||
|
case AvatarEditorAction.ACTION_RANDOMIZE:
|
||||||
|
return;
|
||||||
|
case AvatarEditorAction.ACTION_SAVE:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() =>
|
||||||
|
{
|
||||||
|
const linkTracker: ILinkEventTracker = {
|
||||||
|
linkReceived: (url: string) =>
|
||||||
|
{
|
||||||
|
const parts = url.split('/');
|
||||||
|
|
||||||
|
if(parts.length < 2) return;
|
||||||
|
|
||||||
|
switch(parts[1])
|
||||||
|
{
|
||||||
|
case 'show':
|
||||||
|
setIsVisible(true);
|
||||||
|
return;
|
||||||
|
case 'hide':
|
||||||
|
setIsVisible(false);
|
||||||
|
return;
|
||||||
|
case 'toggle':
|
||||||
|
setIsVisible(prevValue => !prevValue);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
eventUrlPrefix: 'avatar-editor/'
|
||||||
|
};
|
||||||
|
|
||||||
|
AddLinkEventTracker(linkTracker);
|
||||||
|
|
||||||
|
return () => RemoveLinkEventTracker(linkTracker);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if(!isVisible) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<NitroCardView uniqueKey="avatar-editor" className="nitro-avatar-editor">
|
||||||
|
<NitroCardHeaderView headerText={ LocalizeText('avatareditor.title') } onCloseClick={ event => setIsVisible(false) } />
|
||||||
|
<NitroCardTabsView>
|
||||||
|
{ Object.keys(avatarModels).map(modelKey =>
|
||||||
|
{
|
||||||
|
const isActive = (activeModelKey === modelKey);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<NitroCardTabsItemView key={ modelKey } isActive={ isActive } onClick={ event => setActiveModelKey(modelKey) }>
|
||||||
|
{ LocalizeText(`avatareditor.category.${ modelKey }`) }
|
||||||
|
</NitroCardTabsItemView>
|
||||||
|
);
|
||||||
|
}) }
|
||||||
|
</NitroCardTabsView>
|
||||||
|
<NitroCardContentView>
|
||||||
|
<Grid>
|
||||||
|
<Column size={ 9 } overflow="hidden">
|
||||||
|
{ ((activeModelKey.length > 0) && (activeModelKey !== AvatarEditorFigureCategory.WARDROBE)) &&
|
||||||
|
<AvatarEditorModelView name={ activeModelKey } categories={ avatarModels[activeModelKey] } /> }
|
||||||
|
{ (activeModelKey === AvatarEditorFigureCategory.WARDROBE) }
|
||||||
|
</Column>
|
||||||
|
<Column size={ 3 } overflow="hidden">
|
||||||
|
{ /* <AvatarEditorFigurePreviewView figureData={ figureData } /> */ }
|
||||||
|
<Column grow gap={ 1 }>
|
||||||
|
<ButtonGroup>
|
||||||
|
<Button variant="secondary" onClick={ event => processAction(AvatarEditorAction.ACTION_RESET) }>
|
||||||
|
<FaUndo className="fa-icon" />
|
||||||
|
</Button>
|
||||||
|
<Button variant="secondary" onClick={ event => processAction(AvatarEditorAction.ACTION_CLEAR) }>
|
||||||
|
<FaTrash className="fa-icon" />
|
||||||
|
</Button>
|
||||||
|
<Button variant="secondary" onClick={ event => processAction(AvatarEditorAction.ACTION_RANDOMIZE) }>
|
||||||
|
<FaDice className="fa-icon" />
|
||||||
|
</Button>
|
||||||
|
</ButtonGroup>
|
||||||
|
<Button className="w-100" variant="success" onClick={ event => processAction(AvatarEditorAction.ACTION_SAVE) }>
|
||||||
|
{ LocalizeText('avatareditor.save') }
|
||||||
|
</Button>
|
||||||
|
</Column>
|
||||||
|
</Column>
|
||||||
|
</Grid>
|
||||||
|
</NitroCardContentView>
|
||||||
|
</NitroCardView>
|
||||||
|
);
|
||||||
|
}
|
30
src/components/avatar-editor-new/views/AvatarEditorIcon.tsx
Normal file
30
src/components/avatar-editor-new/views/AvatarEditorIcon.tsx
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { FC, useMemo } from 'react';
|
||||||
|
import { Base, BaseProps } from '../../../common';
|
||||||
|
|
||||||
|
type AvatarIconType = 'male' | 'female' | 'clear' | 'sellable' | string;
|
||||||
|
|
||||||
|
export interface AvatarEditorIconProps extends BaseProps<HTMLDivElement>
|
||||||
|
{
|
||||||
|
icon: AvatarIconType;
|
||||||
|
selected?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AvatarEditorIcon: FC<AvatarEditorIconProps> = props =>
|
||||||
|
{
|
||||||
|
const { icon = null, selected = false, classNames = [], children = null, ...rest } = props;
|
||||||
|
|
||||||
|
const getClassNames = useMemo(() =>
|
||||||
|
{
|
||||||
|
const newClassNames: string[] = [ 'nitro-avatar-editor-spritesheet' ];
|
||||||
|
|
||||||
|
if(icon && icon.length) newClassNames.push(icon + '-icon');
|
||||||
|
|
||||||
|
if(selected) newClassNames.push('selected');
|
||||||
|
|
||||||
|
if(classNames.length) newClassNames.push(...classNames);
|
||||||
|
|
||||||
|
return newClassNames;
|
||||||
|
}, [ icon, selected, classNames ]);
|
||||||
|
|
||||||
|
return <Base classNames={ getClassNames } { ...rest } />
|
||||||
|
}
|
@ -0,0 +1,85 @@
|
|||||||
|
import { AvatarEditorFigureCategory } from '@nitrots/nitro-renderer';
|
||||||
|
import { FC, useEffect, useMemo, useState } from 'react';
|
||||||
|
import { FigureData, IAvatarEditorCategory } from '../../../api';
|
||||||
|
import { Column, Flex, Grid } from '../../../common';
|
||||||
|
import { useAvatarEditor } from '../../../hooks';
|
||||||
|
import { AvatarEditorIcon } from './AvatarEditorIcon';
|
||||||
|
import { AvatarEditorFigureSetView } from './figure-set';
|
||||||
|
import { AvatarEditorPaletteSetView } from './palette-set';
|
||||||
|
|
||||||
|
export const AvatarEditorModelView: FC<{
|
||||||
|
name: string,
|
||||||
|
categories: IAvatarEditorCategory[]
|
||||||
|
}> = props =>
|
||||||
|
{
|
||||||
|
const { name = '', categories = [] } = props;
|
||||||
|
const [ activeSetType, setActiveSetType ] = useState<string>('');
|
||||||
|
const { maxPaletteCount = 1 } = useAvatarEditor();
|
||||||
|
|
||||||
|
const activeCategory = useMemo(() =>
|
||||||
|
{
|
||||||
|
return categories.find(category => category.setType === activeSetType) ?? null;
|
||||||
|
}, [ categories, activeSetType ]);
|
||||||
|
|
||||||
|
const setGender = (gender: string) =>
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() =>
|
||||||
|
{
|
||||||
|
if(!activeCategory) return;
|
||||||
|
|
||||||
|
// we need to run this when we change which parts r selected
|
||||||
|
/* for(const partItem of activeCategory.partItems)
|
||||||
|
{
|
||||||
|
if(!partItem || !part.isSelected) continue;
|
||||||
|
|
||||||
|
setMaxPaletteCount(part.maxColorIndex || 1);
|
||||||
|
|
||||||
|
break;
|
||||||
|
} */
|
||||||
|
}, [ activeCategory ])
|
||||||
|
|
||||||
|
useEffect(() =>
|
||||||
|
{
|
||||||
|
if(!categories || !categories.length) return;
|
||||||
|
|
||||||
|
setActiveSetType(categories[0]?.setType)
|
||||||
|
}, [ categories ]);
|
||||||
|
|
||||||
|
if(!activeCategory) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Grid>
|
||||||
|
<Column size={ 2 }>
|
||||||
|
{ (name === AvatarEditorFigureCategory.GENERIC) &&
|
||||||
|
<>
|
||||||
|
<Flex center pointer className="category-item" onClick={ event => setGender(FigureData.MALE) }>
|
||||||
|
<AvatarEditorIcon icon="male" selected={ false } />
|
||||||
|
</Flex>
|
||||||
|
<Flex center pointer className="category-item" onClick={ event => setGender(FigureData.FEMALE) }>
|
||||||
|
<AvatarEditorIcon icon="female" selected={ false } />
|
||||||
|
</Flex>
|
||||||
|
</> }
|
||||||
|
{ (name !== AvatarEditorFigureCategory.GENERIC) && (categories.length > 0) && categories.map(category =>
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
<Flex center pointer key={ category.setType } className="category-item" onClick={ event => setActiveSetType(category.setType) }>
|
||||||
|
<AvatarEditorIcon icon={ category.setType } selected={ (activeSetType === category.setType) } />
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
}) }
|
||||||
|
</Column>
|
||||||
|
<Column size={ 5 } overflow="hidden">
|
||||||
|
<AvatarEditorFigureSetView category={ activeCategory } />
|
||||||
|
</Column>
|
||||||
|
<Column size={ 5 } overflow="hidden">
|
||||||
|
{ (maxPaletteCount >= 1) &&
|
||||||
|
<AvatarEditorPaletteSetView category={ activeCategory } paletteIndex={ 0 } /> }
|
||||||
|
{ (maxPaletteCount === 2) &&
|
||||||
|
<AvatarEditorPaletteSetView category={ activeCategory } paletteIndex={ 1 } /> }
|
||||||
|
</Column>
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,41 @@
|
|||||||
|
import { FC, useEffect, useState } from 'react';
|
||||||
|
import { AvatarEditorThumbnailsHelper, GetConfigurationValue, IAvatarEditorCategoryPartItem } from '../../../../api';
|
||||||
|
import { LayoutCurrencyIcon, LayoutGridItem, LayoutGridItemProps } from '../../../../common';
|
||||||
|
import { AvatarEditorIcon } from '../AvatarEditorIcon';
|
||||||
|
|
||||||
|
export const AvatarEditorFigureSetItemView: FC<{
|
||||||
|
setType: string;
|
||||||
|
partItem: IAvatarEditorCategoryPartItem;
|
||||||
|
isSelected: boolean;
|
||||||
|
} & LayoutGridItemProps> = props =>
|
||||||
|
{
|
||||||
|
const { setType = null, partItem = null, isSelected = false, ...rest } = props;
|
||||||
|
const [ assetUrl, setAssetUrl ] = useState<string>('');
|
||||||
|
|
||||||
|
const isHC = !GetConfigurationValue<boolean>('hc.disabled', false) && ((partItem.partSet?.clubLevel ?? 0) > 0);
|
||||||
|
|
||||||
|
useEffect(() =>
|
||||||
|
{
|
||||||
|
if(!setType || !setType.length || !partItem) return;
|
||||||
|
|
||||||
|
const loadImage = async () =>
|
||||||
|
{
|
||||||
|
const isHC = !GetConfigurationValue<boolean>('hc.disabled', false) && ((partItem.partSet?.clubLevel ?? 0) > 0);
|
||||||
|
const url = await AvatarEditorThumbnailsHelper.build(setType, partItem, partItem.usesColor, isHC);
|
||||||
|
|
||||||
|
if(url && url.length) setAssetUrl(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
loadImage();
|
||||||
|
}, [ setType, partItem ]);
|
||||||
|
|
||||||
|
if(!partItem || !partItem.partSet) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<LayoutGridItem itemImage={ (partItem.isClear ? undefined : assetUrl) } itemActive={ isSelected } style={ { width: '100%' } } { ...rest }>
|
||||||
|
{ !partItem.isClear && isHC && <LayoutCurrencyIcon className="position-absolute end-1 bottom-1" type="hc" /> }
|
||||||
|
{ partItem.isClear && <AvatarEditorIcon icon="clear" /> }
|
||||||
|
{ !partItem.isClear && partItem.partSet.isSellable && <AvatarEditorIcon icon="sellable" position="absolute" className="end-1 bottom-1" /> }
|
||||||
|
</LayoutGridItem>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
import { FC, useRef } from 'react';
|
||||||
|
import { IAvatarEditorCategory, IAvatarEditorCategoryPartItem } from '../../../../api';
|
||||||
|
import { InfiniteGrid } from '../../../../common';
|
||||||
|
import { useAvatarEditor } from '../../../../hooks';
|
||||||
|
import { AvatarEditorFigureSetItemView } from './AvatarEditorFigureSetItemView';
|
||||||
|
|
||||||
|
export const AvatarEditorFigureSetView: FC<{
|
||||||
|
category: IAvatarEditorCategory
|
||||||
|
}> = props =>
|
||||||
|
{
|
||||||
|
const { category = null } = props;
|
||||||
|
const { selectedParts = null, selectPart } = useAvatarEditor();
|
||||||
|
const elementRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
const isPartItemSelected = (partItem: IAvatarEditorCategoryPartItem) =>
|
||||||
|
{
|
||||||
|
if(!category || !category.setType || !selectedParts || !selectedParts[category.setType]) return false;
|
||||||
|
|
||||||
|
const partId = selectedParts[category.setType];
|
||||||
|
|
||||||
|
return (partId === partItem.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
const columnCount = 3;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<InfiniteGrid rows={ category.partItems } columnCount={ columnCount } overscan={ 5 } itemRender={ (item: IAvatarEditorCategoryPartItem) =>
|
||||||
|
{
|
||||||
|
if(!item) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AvatarEditorFigureSetItemView key={ item.id } setType={ category.setType } partItem={ item } isSelected={ isPartItemSelected(item) } onClick={ event => selectPart(category.setType, item.partSet?.id ?? -1) } style={ { width: ~~(100 / columnCount) + '%' } } />
|
||||||
|
)
|
||||||
|
} } />
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,2 @@
|
|||||||
|
export * from './AvatarEditorFigureSetItemView';
|
||||||
|
export * from './AvatarEditorFigureSetView';
|
@ -0,0 +1,27 @@
|
|||||||
|
import { ColorConverter, IPartColor } from '@nitrots/nitro-renderer';
|
||||||
|
import { FC } from 'react';
|
||||||
|
import { GetConfigurationValue } from '../../../../api';
|
||||||
|
import { LayoutCurrencyIcon, LayoutGridItem, LayoutGridItemProps } from '../../../../common';
|
||||||
|
|
||||||
|
export interface AvatarEditorPaletteSetItemProps extends LayoutGridItemProps
|
||||||
|
{
|
||||||
|
setType: string;
|
||||||
|
partColor: IPartColor;
|
||||||
|
isSelected: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
// its disabled if its hc and you dont have it
|
||||||
|
export const AvatarEditorPaletteSetItem: FC<AvatarEditorPaletteSetItemProps> = props =>
|
||||||
|
{
|
||||||
|
const { setType = null, partColor = null, isSelected = false, ...rest } = props;
|
||||||
|
|
||||||
|
if(!partColor) return null;
|
||||||
|
|
||||||
|
const isHC = !GetConfigurationValue<boolean>('hc.disabled', false) && (partColor.clubLevel > 0);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<LayoutGridItem itemHighlight itemColor={ ColorConverter.int2rgb(partColor.rgb) } itemActive={ isSelected } className="clear-bg" { ...rest }>
|
||||||
|
{ isHC && <LayoutCurrencyIcon className="position-absolute end-1 bottom-1" type="hc" /> }
|
||||||
|
</LayoutGridItem>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
import { IPartColor } from '@nitrots/nitro-renderer';
|
||||||
|
import { FC, useRef } from 'react';
|
||||||
|
import { IAvatarEditorCategory } from '../../../../api';
|
||||||
|
import { AutoGrid } from '../../../../common';
|
||||||
|
import { useAvatarEditor } from '../../../../hooks';
|
||||||
|
import { AvatarEditorPaletteSetItem } from './AvatarEditorPaletteSetItemView';
|
||||||
|
|
||||||
|
export const AvatarEditorPaletteSetView: FC<{
|
||||||
|
category: IAvatarEditorCategory,
|
||||||
|
paletteIndex: number;
|
||||||
|
}> = props =>
|
||||||
|
{
|
||||||
|
const { category = null, paletteIndex = -1 } = props;
|
||||||
|
const paletteSet = category?.colorItems[paletteIndex] ?? null;
|
||||||
|
const { selectedColors = null, selectColor } = useAvatarEditor();
|
||||||
|
const elementRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
const isPartColorSelected = (partColor: IPartColor) =>
|
||||||
|
{
|
||||||
|
if(!category || !category.setType || !selectedColors || !selectedColors[category.setType] || !selectedColors[category.setType][paletteIndex]) return false;
|
||||||
|
|
||||||
|
const colorId = selectedColors[category.setType][paletteIndex];
|
||||||
|
|
||||||
|
return (colorId === partColor.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AutoGrid innerRef={ elementRef } gap={ 1 } columnCount={ 5 } columnMinWidth={ 30 }>
|
||||||
|
{ (paletteSet.length > 0) && paletteSet.map(item =>
|
||||||
|
<AvatarEditorPaletteSetItem key={ item.id } setType={ category.setType } partColor={ item } isSelected={ isPartColorSelected(item) } onClick={ event => selectColor(category.setType, paletteIndex, item.id) } />) }
|
||||||
|
</AutoGrid>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,2 @@
|
|||||||
|
export * from './AvatarEditorPaletteSetItemView';
|
||||||
|
export * from './AvatarEditorPaletteSetView';
|
@ -1,4 +1,4 @@
|
|||||||
import { AddLinkEventTracker, AvatarEditorFigureCategory, FigureSetIdsMessageEvent, GetAvatarRenderManager, GetSessionDataManager, GetWardrobeMessageComposer, IAvatarFigureContainer, ILinkEventTracker, RemoveLinkEventTracker, UserFigureComposer, UserWardrobePageEvent } from '@nitrots/nitro-renderer';
|
import { AvatarEditorFigureCategory, FigureSetIdsMessageEvent, GetAvatarRenderManager, GetSessionDataManager, GetWardrobeMessageComposer, IAvatarFigureContainer, 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 { AvatarEditorAction, AvatarEditorUtilities, BodyModel, FigureData, GetClubMemberLevel, GetConfigurationValue, HeadModel, IAvatarEditorCategoryModel, LegModel, LocalizeText, SendMessageComposer, TorsoModel, generateRandomFigure } from '../../api';
|
import { AvatarEditorAction, AvatarEditorUtilities, BodyModel, FigureData, GetClubMemberLevel, GetConfigurationValue, HeadModel, IAvatarEditorCategoryModel, LegModel, LocalizeText, SendMessageComposer, TorsoModel, generateRandomFigure } from '../../api';
|
||||||
@ -148,7 +148,7 @@ export const AvatarEditorView: FC<{}> = props =>
|
|||||||
setFigureData(figures.get(gender));
|
setFigureData(figures.get(gender));
|
||||||
}, [ figures ]);
|
}, [ figures ]);
|
||||||
|
|
||||||
useEffect(() =>
|
/* useEffect(() =>
|
||||||
{
|
{
|
||||||
const linkTracker: ILinkEventTracker = {
|
const linkTracker: ILinkEventTracker = {
|
||||||
linkReceived: (url: string) =>
|
linkReceived: (url: string) =>
|
||||||
@ -176,7 +176,7 @@ export const AvatarEditorView: FC<{}> = props =>
|
|||||||
AddLinkEventTracker(linkTracker);
|
AddLinkEventTracker(linkTracker);
|
||||||
|
|
||||||
return () => RemoveLinkEventTracker(linkTracker);
|
return () => RemoveLinkEventTracker(linkTracker);
|
||||||
}, []);
|
}, []); */
|
||||||
|
|
||||||
useEffect(() =>
|
useEffect(() =>
|
||||||
{
|
{
|
||||||
|
@ -3,6 +3,7 @@ import { FC, useEffect, useState } from 'react';
|
|||||||
import { Base, TransitionAnimation, TransitionAnimationTypes } from '../../common';
|
import { Base, TransitionAnimation, TransitionAnimationTypes } from '../../common';
|
||||||
import { useNitroEvent } from '../../hooks';
|
import { useNitroEvent } from '../../hooks';
|
||||||
import { AchievementsView } from '../achievements/AchievementsView';
|
import { AchievementsView } from '../achievements/AchievementsView';
|
||||||
|
import { AvatarEditorNewView } from '../avatar-editor-new/AvatarEditorView';
|
||||||
import { AvatarEditorView } from '../avatar-editor/AvatarEditorView';
|
import { AvatarEditorView } from '../avatar-editor/AvatarEditorView';
|
||||||
import { CameraWidgetView } from '../camera/CameraWidgetView';
|
import { CameraWidgetView } from '../camera/CameraWidgetView';
|
||||||
import { CampaignView } from '../campaign/CampaignView';
|
import { CampaignView } from '../campaign/CampaignView';
|
||||||
@ -89,6 +90,7 @@ export const MainView: FC<{}> = props =>
|
|||||||
<ChatHistoryView />
|
<ChatHistoryView />
|
||||||
<WiredView />
|
<WiredView />
|
||||||
<AvatarEditorView />
|
<AvatarEditorView />
|
||||||
|
<AvatarEditorNewView />
|
||||||
<AchievementsView />
|
<AchievementsView />
|
||||||
<NavigatorView />
|
<NavigatorView />
|
||||||
<InventoryView />
|
<InventoryView />
|
||||||
|
1
src/hooks/avatar-editor/index.ts
Normal file
1
src/hooks/avatar-editor/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './useAvatarEditor';
|
233
src/hooks/avatar-editor/useAvatarEditor.ts
Normal file
233
src/hooks/avatar-editor/useAvatarEditor.ts
Normal file
@ -0,0 +1,233 @@
|
|||||||
|
import { AvatarEditorFigureCategory, GetAvatarRenderManager, IFigurePartSet, IPartColor } from '@nitrots/nitro-renderer';
|
||||||
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
|
import { useBetween } from 'use-between';
|
||||||
|
import { FigureData, GetClubMemberLevel, GetConfigurationValue, IAvatarEditorCategory, IAvatarEditorCategoryPartItem } from '../../api';
|
||||||
|
|
||||||
|
const MAX_PALETTES: number = 2;
|
||||||
|
|
||||||
|
const useAvatarEditorState = () =>
|
||||||
|
{
|
||||||
|
const [ avatarModels, setAvatarModels ] = useState<{ [index: string]: IAvatarEditorCategory[] }>({});
|
||||||
|
const [ activeModelKey, setActiveModelKey ] = useState<string>('');
|
||||||
|
const [ selectedParts, setSelectedParts ] = useState<{ [index: string]: number }>({});
|
||||||
|
const [ selectedColors, setSelectedColors ] = useState<{ [index: string]: { [index: number]: number }}>({});
|
||||||
|
const [ maxPaletteCount, setMaxPaletteCount ] = useState<number>(1);
|
||||||
|
|
||||||
|
const clubItemsFirst = useMemo(() => GetConfigurationValue<boolean>('avatareditor.show.clubitems.first', true), []);
|
||||||
|
const clubItemsDimmed = useMemo(() => GetConfigurationValue<boolean>('avatareditor.show.clubitems.dimmed', true), []);
|
||||||
|
const activeModel = useMemo(() => (avatarModels[activeModelKey] ?? null), [ activeModelKey, avatarModels ]);
|
||||||
|
|
||||||
|
const selectPart = useCallback((setType: string, partId: number) =>
|
||||||
|
{
|
||||||
|
if(!setType || !setType.length) return;
|
||||||
|
|
||||||
|
const category = activeModel.find(category => (category.setType === setType));
|
||||||
|
|
||||||
|
if(!category || !category.partItems || !category.partItems.length) return;
|
||||||
|
|
||||||
|
const partItem = category.partItems.find(partItem => partItem.id === partId);
|
||||||
|
|
||||||
|
if(!partItem) return;
|
||||||
|
|
||||||
|
if(partItem.isClear)
|
||||||
|
{
|
||||||
|
// clear the part
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(GetClubMemberLevel() < partItem.partSet.clubLevel) return;
|
||||||
|
|
||||||
|
setMaxPaletteCount(partItem.maxPaletteCount || 1);
|
||||||
|
|
||||||
|
setSelectedParts(prevValue =>
|
||||||
|
{
|
||||||
|
const newValue = { ...prevValue };
|
||||||
|
|
||||||
|
newValue[setType] = partItem.id;
|
||||||
|
|
||||||
|
return newValue;
|
||||||
|
});
|
||||||
|
}, [ activeModel ]);
|
||||||
|
|
||||||
|
const selectColor = useCallback((setType: string, paletteId: number, colorId: number) =>
|
||||||
|
{
|
||||||
|
if(!setType || !setType.length) return;
|
||||||
|
|
||||||
|
const category = activeModel.find(category => (category.setType === setType));
|
||||||
|
|
||||||
|
if(!category || !category.colorItems || !category.colorItems.length) return;
|
||||||
|
|
||||||
|
const palette = category.colorItems[paletteId];
|
||||||
|
|
||||||
|
if(!palette || !palette.length) return;
|
||||||
|
|
||||||
|
const partColor = palette.find(partColor => (partColor.id === colorId));
|
||||||
|
|
||||||
|
if(!partColor) return;
|
||||||
|
|
||||||
|
if(GetClubMemberLevel() < partColor.clubLevel) return;
|
||||||
|
|
||||||
|
setSelectedColors(prevValue =>
|
||||||
|
{
|
||||||
|
const newValue = { ...prevValue };
|
||||||
|
|
||||||
|
if(!newValue[setType]) newValue[setType] = {};
|
||||||
|
|
||||||
|
if(!newValue[setType][paletteId]) newValue[setType][paletteId] = -1;
|
||||||
|
|
||||||
|
newValue[setType][paletteId] = partColor.id;
|
||||||
|
|
||||||
|
return newValue;
|
||||||
|
})
|
||||||
|
}, [ activeModel ]);
|
||||||
|
|
||||||
|
useEffect(() =>
|
||||||
|
{
|
||||||
|
const newAvatarModels: { [index: string]: IAvatarEditorCategory[] } = {};
|
||||||
|
|
||||||
|
const buildCategory = (setType: string) =>
|
||||||
|
{
|
||||||
|
const partItems: IAvatarEditorCategoryPartItem[] = [];
|
||||||
|
const colorItems: IPartColor[][] = [];
|
||||||
|
|
||||||
|
for(let i = 0; i < MAX_PALETTES; i++) colorItems.push([]);
|
||||||
|
|
||||||
|
const set = GetAvatarRenderManager().structureData.getSetType(setType);
|
||||||
|
const palette = GetAvatarRenderManager().structureData.getPalette(set.paletteID);
|
||||||
|
|
||||||
|
if(!set || !palette) return null;
|
||||||
|
|
||||||
|
for(const partColor of palette.colors.getValues())
|
||||||
|
{
|
||||||
|
if(!partColor || !partColor.isSelectable) continue;
|
||||||
|
|
||||||
|
for(let i = 0; i < MAX_PALETTES; i++) colorItems[i].push(partColor);
|
||||||
|
|
||||||
|
// TODO - check what this does
|
||||||
|
/* if(setType !== FigureData.FACE)
|
||||||
|
{
|
||||||
|
let i = 0;
|
||||||
|
|
||||||
|
while(i < colorIds.length)
|
||||||
|
{
|
||||||
|
if(partColor.id === colorIds[i]) partColors[i] = partColor;
|
||||||
|
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
} */
|
||||||
|
}
|
||||||
|
|
||||||
|
let mandatorySetIds: string[] = [];
|
||||||
|
|
||||||
|
if(clubItemsDimmed)
|
||||||
|
{
|
||||||
|
//mandatorySetIds = GetAvatarRenderManager().getMandatoryAvatarPartSetIds(this.CURRENT_FIGURE.gender, 2);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//mandatorySetIds = GetAvatarRenderManager().getMandatoryAvatarPartSetIds(this.CURRENT_FIGURE.gender, clubMemberLevel);
|
||||||
|
}
|
||||||
|
|
||||||
|
const isntMandatorySet = (mandatorySetIds.indexOf(setType) === -1);
|
||||||
|
|
||||||
|
if(isntMandatorySet) partItems.push({ id: -1, isClear: true });
|
||||||
|
|
||||||
|
const usesColor = (setType !== FigureData.FACE);
|
||||||
|
const partSets = set.partSets;
|
||||||
|
|
||||||
|
for(let i = (partSets.length); i >= 0; i--)
|
||||||
|
{
|
||||||
|
const partSet = partSets.getWithIndex(i);
|
||||||
|
|
||||||
|
if(!partSet || !partSet.isSelectable) continue;
|
||||||
|
|
||||||
|
let maxPaletteCount = 0;
|
||||||
|
|
||||||
|
for(const part of partSet.parts) maxPaletteCount = Math.max(maxPaletteCount, part.colorLayerIndex);
|
||||||
|
|
||||||
|
partItems.push({ id: partSet.id, partSet, usesColor, maxPaletteCount });
|
||||||
|
}
|
||||||
|
|
||||||
|
partItems.sort(clubItemsFirst ? clubSorter : noobSorter);
|
||||||
|
|
||||||
|
for(let i = 0; i < MAX_PALETTES; i++) colorItems[i].sort(colorSorter);
|
||||||
|
|
||||||
|
return { setType, partItems, colorItems };
|
||||||
|
}
|
||||||
|
|
||||||
|
newAvatarModels[AvatarEditorFigureCategory.GENERIC] = [ FigureData.FACE ].map(setType => buildCategory(setType));
|
||||||
|
newAvatarModels[AvatarEditorFigureCategory.HEAD] = [ FigureData.HAIR, FigureData.HAT, FigureData.HEAD_ACCESSORIES, FigureData.EYE_ACCESSORIES, FigureData.FACE_ACCESSORIES ].map(setType => buildCategory(setType));
|
||||||
|
newAvatarModels[AvatarEditorFigureCategory.TORSO] = [ FigureData.SHIRT, FigureData.CHEST_PRINTS, FigureData.JACKET, FigureData.CHEST_ACCESSORIES ].map(setType => buildCategory(setType));
|
||||||
|
newAvatarModels[AvatarEditorFigureCategory.LEGS] = [ FigureData.TROUSERS, FigureData.SHOES, FigureData.TROUSER_ACCESSORIES ].map(setType => buildCategory(setType));
|
||||||
|
newAvatarModels[AvatarEditorFigureCategory.WARDROBE] = [];
|
||||||
|
|
||||||
|
console.log(newAvatarModels);
|
||||||
|
|
||||||
|
setAvatarModels(newAvatarModels);
|
||||||
|
setActiveModelKey(AvatarEditorFigureCategory.GENERIC);
|
||||||
|
}, [ clubItemsDimmed, clubItemsFirst ]);
|
||||||
|
|
||||||
|
return { avatarModels, activeModelKey, setActiveModelKey, selectedParts, selectedColors, maxPaletteCount, selectPart, selectColor };
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useAvatarEditor = () => useBetween(useAvatarEditorState);
|
||||||
|
|
||||||
|
const clubSorter = (a: { partSet: IFigurePartSet, usesColor: boolean, isClear?: boolean }, b: { partSet: IFigurePartSet, usesColor: boolean, isClear?: boolean }) =>
|
||||||
|
{
|
||||||
|
const clubLevelA = (!a.partSet ? 9999999999 : a.partSet.clubLevel);
|
||||||
|
const clubLevelB = (!b.partSet ? 9999999999 : b.partSet.clubLevel);
|
||||||
|
const isSellableA = (!a.partSet ? false : a.partSet.isSellable);
|
||||||
|
const isSellableB = (!b.partSet ? false : b.partSet.isSellable);
|
||||||
|
|
||||||
|
if(isSellableA && !isSellableB) return 1;
|
||||||
|
|
||||||
|
if(isSellableB && !isSellableA) return -1;
|
||||||
|
|
||||||
|
if(clubLevelA > clubLevelB) return -1;
|
||||||
|
|
||||||
|
if(clubLevelA < clubLevelB) return 1;
|
||||||
|
|
||||||
|
if(a.partSet.id > b.partSet.id) return -1;
|
||||||
|
|
||||||
|
if(a.partSet.id < b.partSet.id) return 1;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const colorSorter = (a: IPartColor, b: IPartColor) =>
|
||||||
|
{
|
||||||
|
const clubLevelA = (!a ? -1 : a.clubLevel);
|
||||||
|
const clubLevelB = (!b ? -1 : b.clubLevel);
|
||||||
|
|
||||||
|
if(clubLevelA < clubLevelB) return -1;
|
||||||
|
|
||||||
|
if(clubLevelA > clubLevelB) return 1;
|
||||||
|
|
||||||
|
if(a.index < b.index) return -1;
|
||||||
|
|
||||||
|
if(a.index > b.index) return 1;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const noobSorter = (a: { partSet: IFigurePartSet, usesColor: boolean, isClear?: boolean }, b: { partSet: IFigurePartSet, usesColor: boolean, isClear?: boolean }) =>
|
||||||
|
{
|
||||||
|
const clubLevelA = (!a.partSet ? -1 : a.partSet.clubLevel);
|
||||||
|
const clubLevelB = (!b.partSet ? -1 : b.partSet.clubLevel);
|
||||||
|
const isSellableA = (!a.partSet ? false : a.partSet.isSellable);
|
||||||
|
const isSellableB = (!b.partSet ? false : b.partSet.isSellable);
|
||||||
|
|
||||||
|
if(isSellableA && !isSellableB) return 1;
|
||||||
|
|
||||||
|
if(isSellableB && !isSellableA) return -1;
|
||||||
|
|
||||||
|
if(clubLevelA < clubLevelB) return -1;
|
||||||
|
|
||||||
|
if(clubLevelA > clubLevelB) return 1;
|
||||||
|
|
||||||
|
if(a.partSet.id < b.partSet.id) return -1;
|
||||||
|
|
||||||
|
if(a.partSet.id > b.partSet.id) return 1;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
export * from './UseMountEffect';
|
export * from './UseMountEffect';
|
||||||
export * from './achievements';
|
export * from './achievements';
|
||||||
|
export * from './avatar-editor';
|
||||||
export * from './camera';
|
export * from './camera';
|
||||||
export * from './catalog';
|
export * from './catalog';
|
||||||
export * from './chat-history';
|
export * from './chat-history';
|
||||||
@ -14,6 +15,10 @@ export * from './navigator';
|
|||||||
export * from './notification';
|
export * from './notification';
|
||||||
export * from './purse';
|
export * from './purse';
|
||||||
export * from './rooms';
|
export * from './rooms';
|
||||||
|
export * from './rooms/engine';
|
||||||
|
export * from './rooms/promotes';
|
||||||
|
export * from './rooms/widgets';
|
||||||
|
export * from './rooms/widgets/furniture';
|
||||||
export * from './session';
|
export * from './session';
|
||||||
export * from './useLocalStorage';
|
export * from './useLocalStorage';
|
||||||
export * from './useSharedVisibility';
|
export * from './useSharedVisibility';
|
||||||
|
23
yarn.lock
23
yarn.lock
@ -180,7 +180,7 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@babel/helper-plugin-utils" "^7.24.0"
|
"@babel/helper-plugin-utils" "^7.24.0"
|
||||||
|
|
||||||
"@babel/runtime@^7.16.7", "@babel/runtime@^7.21.0", "@babel/runtime@^7.22.5", "@babel/runtime@^7.23.2", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.8.7":
|
"@babel/runtime@^7.21.0", "@babel/runtime@^7.22.5", "@babel/runtime@^7.23.2", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.8.7":
|
||||||
version "7.24.1"
|
version "7.24.1"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.1.tgz#431f9a794d173b53720e69a6464abc6f0e2a5c57"
|
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.1.tgz#431f9a794d173b53720e69a6464abc6f0e2a5c57"
|
||||||
integrity sha512-+BIznRzyqBf+2wCTxcKE3wDjfGeCoVE61KSHGpkzqrLi8qxqFwBeUFyId2cxkTmm55fzDGnm0+yCxaxygrLUnQ==
|
integrity sha512-+BIznRzyqBf+2wCTxcKE3wDjfGeCoVE61KSHGpkzqrLi8qxqFwBeUFyId2cxkTmm55fzDGnm0+yCxaxygrLUnQ==
|
||||||
@ -445,11 +445,6 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f"
|
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f"
|
||||||
integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==
|
integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==
|
||||||
|
|
||||||
"@reach/observe-rect@^1.1.0":
|
|
||||||
version "1.2.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/@reach/observe-rect/-/observe-rect-1.2.0.tgz#d7a6013b8aafcc64c778a0ccb83355a11204d3b2"
|
|
||||||
integrity sha512-Ba7HmkFgfQxZqqaeIWWkNK0rEhpxVQHIoVyW1YDSkGsGIXzcaW4deC8B0pZrNSSyLTdIk7y+5olKt5+g0GmFIQ==
|
|
||||||
|
|
||||||
"@react-aria/ssr@^3.5.0":
|
"@react-aria/ssr@^3.5.0":
|
||||||
version "3.9.2"
|
version "3.9.2"
|
||||||
resolved "https://registry.yarnpkg.com/@react-aria/ssr/-/ssr-3.9.2.tgz#01b756965cd6e32b95217f968f513eb3bd6ee44b"
|
resolved "https://registry.yarnpkg.com/@react-aria/ssr/-/ssr-3.9.2.tgz#01b756965cd6e32b95217f968f513eb3bd6ee44b"
|
||||||
@ -551,13 +546,17 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
tslib "^2.4.0"
|
tslib "^2.4.0"
|
||||||
|
|
||||||
"@tanstack/react-virtual@3.0.0-alpha.0":
|
"@tanstack/react-virtual@3.2.0":
|
||||||
version "3.0.0-alpha.0"
|
version "3.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/@tanstack/react-virtual/-/react-virtual-3.0.0-alpha.0.tgz#2ca5a75fa609eca2b2ba622024d3aa3ee097bc30"
|
resolved "https://registry.yarnpkg.com/@tanstack/react-virtual/-/react-virtual-3.2.0.tgz#fb70f9c6baee753a5a0f7618ac886205d5a02af9"
|
||||||
integrity sha512-WpHU/dt34NwZZ8qtiE05TF+nX/b1W6qrWZarO+s8jJFpPVicrTbJKp5Bjt4eSJuk7aYw272oEfsH3ABBRgj+3A==
|
integrity sha512-OEdMByf2hEfDa6XDbGlZN8qO6bTjlNKqjM3im9JG+u3mCL8jALy0T/67oDI001raUUPh1Bdmfn4ZvPOV5knpcg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime" "^7.16.7"
|
"@tanstack/virtual-core" "3.2.0"
|
||||||
"@reach/observe-rect" "^1.1.0"
|
|
||||||
|
"@tanstack/virtual-core@3.2.0":
|
||||||
|
version "3.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@tanstack/virtual-core/-/virtual-core-3.2.0.tgz#874d36135e4badce2719e7bdc556ce240cbaff14"
|
||||||
|
integrity sha512-P5XgYoAw/vfW65byBbJQCw+cagdXDT/qH6wmABiLt4v4YBT2q2vqCOhihe+D1Nt325F/S/0Tkv6C5z0Lv+VBQQ==
|
||||||
|
|
||||||
"@types/babel__core@^7.20.5":
|
"@types/babel__core@^7.20.5":
|
||||||
version "7.20.5"
|
version "7.20.5"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user