From 9ef6750474758258b583f09e0e72785e3221d452 Mon Sep 17 00:00:00 2001 From: Bill Date: Sat, 17 Jul 2021 23:47:19 -0400 Subject: [PATCH] Start avatar editor --- src/views/avatar-editor/AvatarEditorView.tsx | 53 +- .../avatar-editor/common/AvatarEditor.ts | 289 +++++++++++ .../common/AvatarEditorGridColorItem.ts | 52 ++ .../common/AvatarEditorGridPartItem.ts | 320 ++++++++++++ src/views/avatar-editor/common/BodyModel.ts | 80 +++ .../avatar-editor/common/CategoryBaseModel.ts | 248 +++++++++ .../avatar-editor/common/CategoryData.ts | 487 ++++++++++++++++++ src/views/avatar-editor/common/HeadModel.ts | 23 + .../common/IAvatarEditorCategoryModel.ts | 19 + src/views/avatar-editor/common/LegModel.ts | 21 + src/views/avatar-editor/common/TorsoModel.ts | 22 + .../AvatarEditorFigureSetItemView.tsx | 16 + .../AvatarEditorFigureSetItemView.types.ts | 7 + .../figure-set/AvatarEditorFigureSetView.tsx | 28 + .../AvatarEditorFigureSetView.types.ts | 8 + .../views/model/AvatarEditorModelView.tsx | 60 +++ .../model/AvatarEditorModelView.types.ts | 8 + 17 files changed, 1732 insertions(+), 9 deletions(-) create mode 100644 src/views/avatar-editor/common/AvatarEditor.ts create mode 100644 src/views/avatar-editor/common/AvatarEditorGridColorItem.ts create mode 100644 src/views/avatar-editor/common/AvatarEditorGridPartItem.ts create mode 100644 src/views/avatar-editor/common/BodyModel.ts create mode 100644 src/views/avatar-editor/common/CategoryBaseModel.ts create mode 100644 src/views/avatar-editor/common/CategoryData.ts create mode 100644 src/views/avatar-editor/common/HeadModel.ts create mode 100644 src/views/avatar-editor/common/IAvatarEditorCategoryModel.ts create mode 100644 src/views/avatar-editor/common/LegModel.ts create mode 100644 src/views/avatar-editor/common/TorsoModel.ts create mode 100644 src/views/avatar-editor/views/figure-set-item/AvatarEditorFigureSetItemView.tsx create mode 100644 src/views/avatar-editor/views/figure-set-item/AvatarEditorFigureSetItemView.types.ts create mode 100644 src/views/avatar-editor/views/figure-set/AvatarEditorFigureSetView.tsx create mode 100644 src/views/avatar-editor/views/figure-set/AvatarEditorFigureSetView.types.ts create mode 100644 src/views/avatar-editor/views/model/AvatarEditorModelView.tsx create mode 100644 src/views/avatar-editor/views/model/AvatarEditorModelView.types.ts diff --git a/src/views/avatar-editor/AvatarEditorView.tsx b/src/views/avatar-editor/AvatarEditorView.tsx index 0e19abaf..8fe50d8c 100644 --- a/src/views/avatar-editor/AvatarEditorView.tsx +++ b/src/views/avatar-editor/AvatarEditorView.tsx @@ -1,16 +1,28 @@ +import { AvatarEditorFigureCategory } from 'nitro-renderer'; import { FC, useCallback, useEffect, useReducer, useState } from 'react'; import { AvatarEditorEvent } from '../../events/avatar-editor'; import { useUiEvent } from '../../hooks/events/ui/ui-event'; -import { NitroCardContentView, NitroCardHeaderView, NitroCardTabsView, NitroCardView } from '../../layout'; +import { NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView } from '../../layout'; import { LocalizeText } from '../../utils/LocalizeText'; import { AvatarEditorViewProps } from './AvatarEditorView.types'; +import { AvatarEditor } from './common/AvatarEditor'; +import { BodyModel } from './common/BodyModel'; +import { HeadModel } from './common/HeadModel'; +import { IAvatarEditorCategoryModel } from './common/IAvatarEditorCategoryModel'; +import { LegModel } from './common/LegModel'; +import { TorsoModel } from './common/TorsoModel'; import { AvatarEditorContextProvider } from './context/AvatarEditorContext'; import { AvatarEditorReducer, initialAvatarEditor } from './reducers/AvatarEditorReducer'; +import { AvatarEditorModelView } from './views/model/AvatarEditorModelView'; export const AvatarEditorView: FC = props => { const [ isVisible, setIsVisible ] = useState(false); const [ avatarEditorState, dispatchAvatarEditorState ] = useReducer(AvatarEditorReducer, initialAvatarEditor); + const [ avatarEditor, setAvatarEditor ] = useState(null); + const [ categories, setCategories ] = useState>(null); + const [ activeCategory, setActiveCategory ] = useState(null); + const [ isInitalized, setIsInitalized ] = useState(false); const onAvatarEditorEvent = useCallback((event: AvatarEditorEvent) => { @@ -32,10 +44,30 @@ export const AvatarEditorView: FC = props => useUiEvent(AvatarEditorEvent.HIDE_EDITOR, onAvatarEditorEvent); useUiEvent(AvatarEditorEvent.TOGGLE_EDITOR, onAvatarEditorEvent); + const selectCategory = useCallback((name: string) => + { + setActiveCategory(categories.get(name)); + }, [ categories ]); + useEffect(() => { - if(!isVisible) return; - }, [ isVisible ]); + if(!isVisible || isInitalized) return; + + const newEditor = new AvatarEditor(); + + setAvatarEditor(newEditor); + + const categories = new Map(); + + categories.set(AvatarEditorFigureCategory.GENERIC, new BodyModel(newEditor)); + categories.set(AvatarEditorFigureCategory.HEAD, new HeadModel(newEditor)); + categories.set(AvatarEditorFigureCategory.TORSO, new TorsoModel(newEditor)); + categories.set(AvatarEditorFigureCategory.LEGS, new LegModel(newEditor)); + + setCategories(categories); + setActiveCategory(categories.get(AvatarEditorFigureCategory.GENERIC)); + setIsInitalized(true); + }, [ isVisible, isInitalized ]); return ( @@ -43,14 +75,17 @@ export const AvatarEditorView: FC = props => setIsVisible(false) } /> + { categories && Array.from(categories.keys()).map(category => + { + return ( + selectCategory(category) }> + { LocalizeText(`avatareditor.category.${ category }`) } + + ); + })} -
-
-
-
-
-
+ { activeCategory && }
}
diff --git a/src/views/avatar-editor/common/AvatarEditor.ts b/src/views/avatar-editor/common/AvatarEditor.ts new file mode 100644 index 00000000..4bd882b6 --- /dev/null +++ b/src/views/avatar-editor/common/AvatarEditor.ts @@ -0,0 +1,289 @@ +import { FigureData, IPalette, IPartColor, ISetType, IStructureData } from 'nitro-renderer'; +import { GetAvatarRenderManager, GetConfiguration, GetSessionDataManager } from '../../../api'; +import { AvatarEditorGridColorItem } from './AvatarEditorGridColorItem'; +import { AvatarEditorGridPartItem } from './AvatarEditorGridPartItem'; +import { CategoryBaseModel } from './CategoryBaseModel'; +import { CategoryData } from './CategoryData'; + +const MAX_PALETTES: number = 2; +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 class AvatarEditor +{ + private _figureStructureData: IStructureData; + private _figures: Map; + private _gender: string; + + constructor() + { + this._figureStructureData = GetAvatarRenderManager().structureData; + this._figures = new Map(); + this._gender = FigureData.MALE; + + const maleFigure = new FigureData(); + const femaleFigure = new FigureData(); + + maleFigure.loadAvatarData(DEFAULT_MALE_FIGURE, FigureData.MALE); + femaleFigure.loadAvatarData(DEFAULT_FEMALE_FIGURE, FigureData.FEMALE); + + this._figures.set(FigureData.MALE, maleFigure); + this._figures.set(FigureData.FEMALE, femaleFigure); + } + + public getSetType(setType: string): ISetType + { + if(!this._figureStructureData) return null; + + return this._figureStructureData.getSetType(setType); + } + + public getPalette(paletteId: number): IPalette + { + if(!this._figureStructureData) return null; + + return this._figureStructureData.getPalette(paletteId); + } + + public createCategory(model: CategoryBaseModel, name: string): CategoryData + { + if(!model || !name) return null; + + const partItems: AvatarEditorGridPartItem[] = []; + const colorItems: AvatarEditorGridColorItem[][] = []; + + let i = 0; + + while(i < MAX_PALETTES) + { + colorItems.push([]); + + i++; + } + + const setType = this.getSetType(name); + + if(!setType) return null; + + const palette = this.getPalette(setType.paletteID); + + if(!palette) return null; + + let colorIds = this.figureData.getColourIds(name); + + if(!colorIds) colorIds = []; + + const partColors: IPartColor[] = new Array(colorIds.length); + const clubItemsDimmed = this.clubItemsDimmed; + + for(const partColor of palette.colors.values()) + { + if(partColor.isSelectable && (clubItemsDimmed || (this.clubMemberLevel >= partColor.clubLevel))) + { + let i = 0; + + while(i < MAX_PALETTES) + { + const isDisabled = (this.clubMemberLevel < partColor.clubLevel); + const colorItem = new AvatarEditorGridColorItem(partColor, isDisabled); + + colorItems[i].push(colorItem); + + i++; + } + + if(name !== 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._gender, 2); + } + else + { + mandatorySetIds = GetAvatarRenderManager().getMandatoryAvatarPartSetIds(this._gender, this.clubMemberLevel); + } + + const isntMandatorySet = (mandatorySetIds.indexOf(name) === -1); + + if(isntMandatorySet) + { + const partItem = new AvatarEditorGridPartItem(null, null, false); + + partItem.isClear = true; + + partItems.push(partItem); + } + + const usesColors = (name !== FigureData.FACE); + const partSets = setType.partSets; + const totalPartSets = partSets.length; + + i = (totalPartSets - 1); + + while(i >= 0) + { + const partSet = partSets.getWithIndex(i); + + let isValidGender = false; + + if(partSet.gender === FigureData.UNISEX) + { + isValidGender = true; + } + else + { + if(partSet.gender === this._gender) + { + isValidGender = true; + } + } + + if(partSet.isSelectable && isValidGender && (clubItemsDimmed || (this.clubMemberLevel >= partSet.clubLevel))) + { + const isDisabled = (this.clubMemberLevel < partSet.clubLevel); + + let isValid = true; + + if(partSet.isSellable) + { + //isValid = (this._inventoryService && this._inventoryService.hasFigureSetId(partSet.id)); + } + + if(isValid) + { + partItems.push(new AvatarEditorGridPartItem(partSet, partColors, usesColors, isDisabled)); + } + } + + i--; + } + + partItems.sort(this.clubItemsFirst ? this.clubSorter : this.noobSorter); + + // if(this._forceSellableClothingVisibility || Nitro.instance.getConfiguration("avatareditor.support.sellablefurni", false)) + // { + // _local_31 = (this._manager.windowManager.assets.getAssetByName("camera_zoom_in") as BitmapDataAsset); + // _local_32 = (_local_31.content as BitmapData).clone(); + // _local_33 = (AvatarEditorView._Str_6802.clone() as IWindowContainer); + // _local_33.name = AvatarEditorGridView.GET_MORE; + // _local_7 = new AvatarEditorGridPartItem(_local_33, k, null, null, false); + // _local_7._Str_3093 = _local_32; + // _local_3.push(_local_7); + // } + + i = 0; + + while(i < MAX_PALETTES) + { + colorItems[i].sort(this.colorSorter); + + i++; + } + + return new CategoryData(name, partItems, colorItems); + } + + private clubSorter(a: AvatarEditorGridPartItem, b: AvatarEditorGridPartItem): number + { + 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; + } + + private noobSorter(a: AvatarEditorGridPartItem, b: AvatarEditorGridPartItem): number + { + 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; + } + + private colorSorter(a: AvatarEditorGridColorItem, b: AvatarEditorGridColorItem): number + { + const clubLevelA = (!a.partColor ? -1 : a.partColor.clubLevel); + const clubLevelB = (!b.partColor ? -1 : b.partColor.clubLevel); + + if(clubLevelA < clubLevelB) return -1; + + if(clubLevelA > clubLevelB) return 1; + + if(a.partColor.index < b.partColor.index) return -1; + + if(a.partColor.index > b.partColor.index) return 1; + + return 0; + } + + public get clubMemberLevel(): number + { + return GetSessionDataManager().clubLevel; + } + + private get clubItemsFirst(): boolean + { + return GetConfiguration('avatareditor.show.clubitems.first', true); + } + + private get clubItemsDimmed(): boolean + { + return GetConfiguration('avatareditor.show.clubitems.dimmed', true); + } + + public get figureData(): FigureData + { + return this._figures.get(this._gender); + } + + public get gender(): string + { + return this._gender; + } + + public set gender(gender: string) + { + this._gender = gender; + } +} diff --git a/src/views/avatar-editor/common/AvatarEditorGridColorItem.ts b/src/views/avatar-editor/common/AvatarEditorGridColorItem.ts new file mode 100644 index 00000000..b6e99efd --- /dev/null +++ b/src/views/avatar-editor/common/AvatarEditorGridColorItem.ts @@ -0,0 +1,52 @@ +import { ColorConverter, IPartColor } from 'nitro-renderer'; + +export class AvatarEditorGridColorItem +{ + private _partColor: IPartColor; + private _isDisabled: boolean; + private _isHC: boolean; + private _isSelected: boolean; + + constructor(partColor: IPartColor, isDisabled: boolean = false) + { + this._partColor = partColor; + this._isDisabled = isDisabled; + this._isHC = (this._partColor.clubLevel > 0); + this._isSelected = false; + } + + public dispose(): void + { + this._partColor = null; + } + + public get partColor(): IPartColor + { + return this._partColor; + } + + public get color(): string + { + return ColorConverter.int2rgb(this._partColor.rgb); + } + + public get isDisabled(): boolean + { + return this._isDisabled; + } + + public get isHC(): boolean + { + return this._isHC; + } + + public get isSelected(): boolean + { + return this._isSelected; + } + + public set isSelected(flag: boolean) + { + this._isSelected = flag; + } +} diff --git a/src/views/avatar-editor/common/AvatarEditorGridPartItem.ts b/src/views/avatar-editor/common/AvatarEditorGridPartItem.ts new file mode 100644 index 00000000..f22b3718 --- /dev/null +++ b/src/views/avatar-editor/common/AvatarEditorGridPartItem.ts @@ -0,0 +1,320 @@ +import { AvatarFigurePartType, FigureData, IAvatarImageListener, IAvatarRenderManager, IFigurePart, IFigurePartSet, IGraphicAsset, IPartColor, NitroContainer, NitroSprite, TextureUtils } from 'nitro-renderer'; +import { GetAvatarRenderManager } from '../../../api'; + +export class AvatarEditorGridPartItem implements IAvatarImageListener +{ + private static THUMB_DIRECTIONS: number[] = [2, 6, 0, 4, 3, 1]; + 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 _renderManager: IAvatarRenderManager; + private _partSet: IFigurePartSet; + private _partColors: IPartColor[]; + private _useColors: boolean; + private _isDisabled: boolean; + private _thumbContainer: NitroContainer; + private _imageUrl: string; + private _maxColorIndex: number; + private _isValidFigure: boolean; + private _isHC: boolean; + private _isSellable: boolean; + private _isClear: boolean; + private _isSelected: boolean; + private _disposed: boolean; + private _isInitalized: boolean; + + constructor(partSet: IFigurePartSet, partColors: IPartColor[], useColors: boolean = true, isDisabled: boolean = false) + { + this._renderManager = GetAvatarRenderManager(); + this._partSet = partSet; + this._partColors = partColors; + this._useColors = useColors; + this._isDisabled = isDisabled; + this._thumbContainer = null; + this._imageUrl = null; + this._maxColorIndex = 0; + this._isValidFigure = false; + this._isHC = false; + this._isSellable = false; + this._isClear = false; + this._isSelected = false; + this._disposed = false; + this._isInitalized = false; + + if(partSet) + { + const colors = partSet.parts; + + for(const color of colors) this._maxColorIndex = Math.max(this._maxColorIndex, color.colorLayerIndex); + } + } + + public init(): void + { + if(this._isInitalized) return; + + this._isInitalized = true; + + this.update(); + } + + public dispose(): void + { + if(this._disposed) return; + + this._renderManager = null; + this._partSet = null; + this._partColors = null; + this._imageUrl = null; + this._disposed = true; + this._isInitalized = false; + + if(this._thumbContainer) + { + this._thumbContainer.destroy(); + + this._thumbContainer = null; + } + } + + public update(): void + { + this.updateThumbVisualization(); + } + + private analyzeFigure(): boolean + { + if(!this._renderManager || !this._partSet || !this._partSet.parts || !this._partSet.parts.length) return false; + + const figureContainer = this._renderManager.createFigureContainer(((this.partSet.type + '-') + this.partSet.id)); + + if(!this._renderManager.isFigureContainerReady(figureContainer)) + { + this._renderManager.downloadAvatarFigure(figureContainer, this); + + return false; + } + + this._isValidFigure = true; + + return true; + } + + private renderThumb(): NitroContainer + { + if(!this._renderManager || !this._partSet) return null; + + if(!this._isValidFigure) + { + if(!this.analyzeFigure()) return null; + } + + const parts = this._partSet.parts.concat().sort(this.sortByDrawOrder); + const container = new NitroContainer(); + + for(const part of parts) + { + if(!part) continue; + + let asset: IGraphicAsset = null; + let direction = 0; + let hasAsset = false; + + while(!hasAsset && (direction < AvatarEditorGridPartItem.THUMB_DIRECTIONS.length)) + { + const assetName = ((((((((((FigureData.SCALE + '_') + FigureData.STD) + '_') + part.type) + '_') + part.id) + '_') + AvatarEditorGridPartItem.THUMB_DIRECTIONS[direction]) + '_') + FigureData.DEFAULT_FRAME); + + asset = this._renderManager.getAssetByName(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(this._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; + + container.addChild(sprite); + } + + return container; + } + + private updateThumbVisualization(): void + { + if(!this._isInitalized) return; + + let container = this._thumbContainer; + + if(!container) container = this.renderThumb(); + + if(!container) return; + + if(this._partSet) + { + this._isHC = (this._partSet.clubLevel > 0); + this._isSellable = this._partSet.isSellable; + } + else + { + this._isHC = false; + this._isSellable = false; + } + + if(this._isDisabled) this.setAlpha(container, 0.2); + + this._imageUrl = TextureUtils.generateImageUrl(container); + } + + private setAlpha(container: NitroContainer, alpha: number): NitroContainer + { + container.alpha = alpha; + + return container; + } + + private sortByDrawOrder(a: IFigurePart, b: IFigurePart): number + { + const indexA = AvatarEditorGridPartItem.DRAW_ORDER.indexOf(a.type); + const indexB = AvatarEditorGridPartItem.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; + } + + public resetFigure(figure: string): void + { + if(!this.analyzeFigure()) return; + + this.update(); + } + + public get disposed(): boolean + { + return this._disposed; + } + + public get id(): number + { + if(!this._partSet) return -1; + + return this._partSet.id; + } + + public get partSet(): IFigurePartSet + { + return this._partSet; + } + + public set colors(partColors: IPartColor[]) + { + this._partColors = partColors; + + this.update(); + } + + public get isDisabledForWearing(): boolean + { + return this._isDisabled; + } + + public set iconImage(k: NitroContainer) + { + this._thumbContainer = k; + + this.update(); + } + + public get imageUrl(): string + { + return this._imageUrl; + } + + public get colorLayerCount(): number + { + return this._maxColorIndex; + } + + public get isHC(): boolean + { + return this._isHC; + } + + public get isSellable(): boolean + { + return this._isSellable; + } + + public get isClear(): boolean + { + return this._isClear; + } + + public set isClear(flag: boolean) + { + this._isClear = flag; + } + + public get isSelected(): boolean + { + return this._isSelected; + } + + public set isSelected(flag: boolean) + { + this._isSelected = flag; + } +} diff --git a/src/views/avatar-editor/common/BodyModel.ts b/src/views/avatar-editor/common/BodyModel.ts new file mode 100644 index 00000000..b1419c10 --- /dev/null +++ b/src/views/avatar-editor/common/BodyModel.ts @@ -0,0 +1,80 @@ +import { AvatarEditorFigureCategory, AvatarScaleType, AvatarSetType, FigureData, IAvatarImageListener } from 'nitro-renderer'; +import { GetAvatarRenderManager } from '../../../api'; +import { CategoryBaseModel } from './CategoryBaseModel'; + +export class BodyModel extends CategoryBaseModel implements IAvatarImageListener +{ + private _imageCallBackHandled: boolean = false; + + public init(): void + { + super.init(); + + this.addCategory(FigureData.FACE); + + this._isInitalized = true; + } + + public selectColor(category: string, colorIndex: number, paletteId: number): void + { + super.selectColor(category, colorIndex, paletteId); + + this.updateSelectionsFromFigure(FigureData.FACE); + } + + protected updateSelectionsFromFigure(name: string): void + { + if(!this._categories || !this._editor || !this._editor.figureData) return; + + const category = this._categories.get(name); + + if(!category) return; + + const setId = this._editor.figureData.getPartSetId(name); + + let colorIds = this._editor.figureData.getColourIds(name); + + if(!colorIds) colorIds = []; + + category.selectPartId(setId); + category.selectColorIds(colorIds); + + for(const part of category.parts) + { + const figure = this._editor.figureData.getFigureStringWithFace(part.id); + const avatarImage = GetAvatarRenderManager().createAvatarImage(figure, AvatarScaleType.LARGE, null, this); + + const sprite = avatarImage.getImageAsSprite(AvatarSetType.HEAD); + + if(sprite) + { + sprite.y = 10; + + part.iconImage = sprite; + + setTimeout(() => avatarImage.dispose(), 0); + } + } + + // if (this._Str_2271) this._Str_2271._Str_5614(k, _local_4.length); + } + + public resetFigure(figure: string): void + { + if(this._imageCallBackHandled) return; + + this._imageCallBackHandled = true; + + this.updateSelectionsFromFigure(FigureData.FACE); + } + + public get canSetGender(): boolean + { + return true; + } + + public get name(): string + { + return AvatarEditorFigureCategory.GENERIC; + } +} diff --git a/src/views/avatar-editor/common/CategoryBaseModel.ts b/src/views/avatar-editor/common/CategoryBaseModel.ts new file mode 100644 index 00000000..d7ea50a2 --- /dev/null +++ b/src/views/avatar-editor/common/CategoryBaseModel.ts @@ -0,0 +1,248 @@ +import { AvatarEditor } from './AvatarEditor'; +import { CategoryData } from './CategoryData'; +import { IAvatarEditorCategoryModel } from './IAvatarEditorCategoryModel'; + +export class CategoryBaseModel implements IAvatarEditorCategoryModel +{ + protected _editor: AvatarEditor; + protected _categories: Map; + protected _isInitalized: boolean; + protected _maxPaletteCount: number; + private _disposed: boolean; + + constructor(editor: AvatarEditor) + { + this._editor = editor; + this._isInitalized = false; + this._maxPaletteCount = 0; + } + + public dispose(): void + { + this._categories = null; + this._disposed = true; + } + + public get disposed(): boolean + { + return this._disposed; + } + + public init(): void + { + if(!this._categories) this._categories = new Map(); + } + + public reset(): void + { + this._isInitalized = false; + + if(this._categories) + { + for(const category of this._categories.values()) (category && category.dispose()); + } + + this._categories = new Map(); + } + + protected addCategory(name: string): void + { + let existing = this._categories.get(name); + + if(existing) return; + + existing = this._editor.createCategory(this, name); + + if(!existing) return; + + this._categories.set(name, existing); + + this.updateSelectionsFromFigure(name); + } + + protected updateSelectionsFromFigure(figure: string): void + { + const category = this._categories.get(figure); + + if(!category) return; + + const setId = this._editor.figureData.getPartSetId(figure); + + let colorIds = this._editor.figureData.getColourIds(figure); + + if(!colorIds) colorIds = []; + + category.selectPartId(setId); + category.selectColorIds(colorIds); + } + + public hasClubSelectionsOverLevel(level: number): boolean + { + if(!this._categories) return false; + + for(const category of this._categories.values()) + { + if(!category) continue; + + if(category.hasClubSelectionsOverLevel(level)) return true; + } + + return false; + } + + public hasInvalidSelectedItems(ownedItems: number[]): boolean + { + if(!this._categories) return false; + + for(const category of this._categories.values()) + { + if(category.hasInvalidSelectedItems(ownedItems)) return true; + } + + return false; + } + + public stripClubItemsOverLevel(level: number): boolean + { + if(!this._categories) return false; + + let didStrip = false; + + for(const [ name, category ] of this._categories.entries()) + { + let isValid = false; + + if(category.stripClubItemsOverLevel(level)) isValid = true; + + if(category.stripClubColorsOverLevel(level)) isValid = true; + + if(isValid) + { + const partItem = category.getCurrentPart(); + + if(partItem && this._editor && this._editor.figureData) + { + this._editor.figureData.savePartData(name, partItem.id, category.getSelectedColorIds(), true); + } + + didStrip = true; + } + } + + return didStrip; + } + + public stripInvalidSellableItems(): boolean + { + if(!this._categories) return false; + + let didStrip = false; + + for(const [ name, category ] of this._categories.entries()) + { + const isValid = false; + + // if(category._Str_8360(this._Str_2278.manager.inventory)) _local_6 = true; + + if(isValid) + { + const partItem = category.getCurrentPart(); + + if(partItem && this._editor && this._editor.figureData) + { + this._editor.figureData.savePartData(name, partItem.id, category.getSelectedColorIds(), true); + } + + didStrip = true; + } + } + + return didStrip; + } + + public selectPart(category: string, partIndex: number): void + { + const categoryData = this._categories.get(category); + + if(!categoryData) return; + + const selectedPartIndex = categoryData.selectedPartIndex; + + categoryData.selectPartIndex(partIndex); + + const partItem = categoryData.getCurrentPart(); + + if(!partItem) return; + + if(partItem.isDisabledForWearing) + { + categoryData.selectPartIndex(selectedPartIndex); + + // open hc window + + return; + } + + this._maxPaletteCount = partItem.colorLayerCount; + + this._editor.figureData.savePartData(category, partItem.id, categoryData.getSelectedColorIds(), true); + } + + public selectColor(category: string, colorIndex: number, paletteId: number): void + { + const categoryData = this._categories.get(category); + + if(!categoryData) return; + + const paletteIndex = categoryData.getCurrentColorIndex(paletteId); + + categoryData.selectColorIndex(colorIndex, paletteId); + + const colorItem = categoryData.getSelectedColor(paletteId); + + if(colorItem.isDisabled) + { + categoryData.selectColorIndex(paletteIndex, paletteId); + + // open hc window + + return; + } + + this._editor.figureData.savePartSetColourId(category, categoryData.getSelectedColorIds(), true); + } + + public getCategoryData(category: string): CategoryData + { + if(!this._isInitalized) this.init(); + + if(!this._categories) return null; + + return this._categories.get(category); + } + + public get categories(): Map + { + return this._categories; + } + + public get canSetGender(): boolean + { + return false; + } + + public get maxPaletteCount(): number + { + return this._maxPaletteCount; + } + + public set maxPaletteCount(count: number) + { + this._maxPaletteCount = count; + } + + public get name(): string + { + return null; + } +} diff --git a/src/views/avatar-editor/common/CategoryData.ts b/src/views/avatar-editor/common/CategoryData.ts new file mode 100644 index 00000000..54ffaa89 --- /dev/null +++ b/src/views/avatar-editor/common/CategoryData.ts @@ -0,0 +1,487 @@ +import { IPartColor } from 'nitro-renderer'; +import { AvatarEditorGridColorItem } from './AvatarEditorGridColorItem'; +import { AvatarEditorGridPartItem } from './AvatarEditorGridPartItem'; + +export class CategoryData +{ + private _name: string; + private _parts: AvatarEditorGridPartItem[]; + private _palettes: AvatarEditorGridColorItem[][]; + private _selectedPartIndex: number = -1; + private _paletteIndexes: number[]; + + constructor(name: string, partItems: AvatarEditorGridPartItem[], colorItems: AvatarEditorGridColorItem[][]) + { + this._name = name; + this._parts = partItems; + this._palettes = colorItems; + this._selectedPartIndex = -1; + } + + private static defaultColorId(palettes: AvatarEditorGridColorItem[], clubLevel: number): number + { + if(!palettes || !palettes.length) return -1; + + let i = 0; + + while(i < palettes.length) + { + const colorItem = palettes[i]; + + if(colorItem.partColor && (colorItem.partColor.clubLevel <= clubLevel)) + { + return colorItem.partColor.id; + } + + i++; + } + + return -1; + } + + public init(): void + { + for(const part of this._parts) + { + if(!part) continue; + + part.init(); + } + } + + public dispose(): void + { + if(this._parts) + { + for(const part of this._parts) part.dispose(); + + this._parts = null; + } + + if(this._palettes) + { + for(const palette of this._palettes) for(const colorItem of palette) colorItem.dispose(); + + this._palettes = null; + } + + this._selectedPartIndex = -1; + this._paletteIndexes = null; + } + + public selectPartId(partId: number): void + { + if(!this._parts) return; + + let i = 0; + + while(i < this._parts.length) + { + const partItem = this._parts[i]; + + if(partItem.id === partId) + { + this.selectPartIndex(i); + + return; + } + + i++; + } + } + + public selectColorIds(colorIds: number[]): void + { + if(!colorIds || !this._palettes) return; + + this._paletteIndexes = new Array(colorIds.length); + + let i = 0; + + while(i < this._palettes.length) + { + const palette = this.getPalette(i); + + if(palette) + { + let colorId = 0; + + if(colorIds.length > i) + { + colorId = colorIds[i]; + } + else + { + const colorItem = palette[0]; + + if(colorItem && colorItem.partColor) colorId = colorItem.partColor.id; + } + + let j = 0; + + while(j < palette.length) + { + const colorItem = palette[j]; + + if(colorItem.partColor.id === colorId) + { + this._paletteIndexes[i] = j; + + colorItem.isSelected = true; + } + else + { + colorItem.isSelected = false; + } + + j++; + } + } + + i++; + } + + this.updatePartColors(); + } + + public selectPartIndex(partIndex: number): AvatarEditorGridPartItem + { + if(!this._parts) return null; + + if((this._selectedPartIndex >= 0) && (this._parts.length > this._selectedPartIndex)) + { + const partItem = this._parts[this._selectedPartIndex]; + + if(partItem) partItem.isSelected = false; + } + + if(this._parts.length > partIndex) + { + const partItem = this._parts[partIndex]; + + if(partItem) + { + partItem.isSelected = true; + + this._selectedPartIndex = partIndex; + + return partItem; + } + } + + return null; + } + + public selectColorIndex(colorIndex: number, paletteId: number): AvatarEditorGridColorItem + { + const palette = this.getPalette(paletteId); + + if(!palette) return null; + + if(palette.length <= colorIndex) return null; + + this.deselectColorIndex(this._paletteIndexes[paletteId], paletteId); + + this._paletteIndexes[paletteId] = colorIndex; + + const colorItem = palette[colorIndex]; + + if(!colorItem) return null; + + colorItem.isSelected = true; + + this.updatePartColors(); + + return colorItem; + } + + public getCurrentColorIndex(k: number): number + { + return this._paletteIndexes[k]; + } + + private deselectColorIndex(colorIndex: number, paletteIndex: number): void + { + const palette = this.getPalette(paletteIndex); + + if(!palette) return; + + if(palette.length <= colorIndex) return; + + const colorItem = palette[colorIndex]; + + if(!colorItem) return; + + colorItem.isSelected = false; + } + + public getSelectedColorIds(): number[] + { + if(!this._paletteIndexes || !this._paletteIndexes.length) return null; + + if(!this._palettes || !this._palettes.length) return null; + + const palette = this._palettes[0]; + + if(!palette || (!palette.length)) return null; + + const colorItem = palette[0]; + + if(!colorItem || !colorItem.partColor) return null; + + const colorId = colorItem.partColor.id; + const colorIds: number[] = []; + + let i = 0; + + while(i < this._paletteIndexes.length) + { + const paletteSet = this._palettes[i]; + + if(!((!(paletteSet)) || (paletteSet.length <= i))) + { + if(paletteSet.length > this._paletteIndexes[i]) + { + const color = paletteSet[this._paletteIndexes[i]]; + + if(color && color.partColor) + { + colorIds.push(color.partColor.id); + } + else + { + colorIds.push(colorId); + } + } + else + { + colorIds.push(colorId); + } + } + + i++; + } + + const partItem = this.getCurrentPart(); + + if(!partItem) return null; + + return colorIds.slice(0, Math.max(partItem.colorLayerCount, 1)); + } + + private getSelectedColors(): IPartColor[] + { + const partColors: IPartColor[] = []; + + let i = 0; + + while(i < this._paletteIndexes.length) + { + const colorItem = this.getSelectedColor(i); + + if(colorItem) + { + partColors.push(colorItem.partColor); + } + else + { + partColors.push(null); + } + + i++; + } + + return partColors; + } + + public getSelectedColor(paletteId: number): AvatarEditorGridColorItem + { + const palette = this.getPalette(paletteId); + + if(!palette || (palette.length <= this._paletteIndexes[paletteId])) return null; + + return palette[this._paletteIndexes[paletteId]]; + } + + public getSelectedColorId(paletteId: number): number + { + const colorItem = this.getSelectedColor(paletteId); + + if(colorItem && (colorItem.partColor)) return colorItem.partColor.id; + + return 0; + } + + public getPalette(paletteId: number): AvatarEditorGridColorItem[] + { + if(!this._paletteIndexes || !this._palettes || (this._palettes.length <= paletteId)) + { + return null; + } + + return this._palettes[paletteId]; + } + + public getCurrentPart(): AvatarEditorGridPartItem + { + return this._parts[this._selectedPartIndex] as AvatarEditorGridPartItem; + } + + private updatePartColors(): void + { + const partColors = this.getSelectedColors(); + + for(const partItem of this._parts) + { + if(partItem) partItem.colors = partColors; + } + } + + public hasClubSelectionsOverLevel(level: number): boolean + { + let hasInvalidSelections = false; + + const partColors = this.getSelectedColors(); + + if(partColors) + { + let i = 0; + + while(i < partColors.length) + { + const partColor = partColors[i]; + + if(partColor && (partColor.clubLevel > level)) hasInvalidSelections = true; + + i++; + } + } + + const partItem = this.getCurrentPart(); + + if(partItem && partItem.partSet) + { + const partSet = partItem.partSet; + + if(partSet && (partSet.clubLevel > level)) hasInvalidSelections = true; + } + + return hasInvalidSelections; + } + + public hasInvalidSelectedItems(ownedItems: number[]): boolean + { + const part = this.getCurrentPart(); + + if(!part) return false; + + const partSet = part.partSet; + + if(!partSet || !partSet.isSellable) return; + + return (ownedItems.indexOf(partSet.id) > -1); + } + + public stripClubItemsOverLevel(level: number): boolean + { + const partItem = this.getCurrentPart(); + + if(partItem && partItem.partSet) + { + const partSet = partItem.partSet; + + if(partSet.clubLevel > level) + { + const newPartItem = this.selectPartIndex(0); + + if(newPartItem && !newPartItem.partSet) this.selectPartIndex(1); + + return true; + } + } + + return false; + } + + public stripClubColorsOverLevel(level: number): boolean + { + const colorIds: number[] = []; + const partColors = this.getSelectedColors(); + const colorItems = this.getPalette(0); + + let didStrip = false; + + const colorId = CategoryData.defaultColorId(colorItems, level); + + if(colorId === -1) return false; + + let i = 0; + + while(i < partColors.length) + { + const partColor = partColors[i]; + + if(!partColor) + { + colorIds.push(colorId); + + didStrip = true; + } + else + { + if(partColor.clubLevel > level) + { + colorIds.push(colorId); + + didStrip = true; + } + else + { + colorIds.push(partColor.id); + } + } + + i++; + } + + if(didStrip) this.selectColorIds(colorIds); + + return didStrip; + } + + // public stripInvalidSellableItems(k:IHabboInventory): boolean + // { + // var _local_3:IFigurePartSet; + // var _local_4:AvatarEditorGridPartItem; + // var _local_2:AvatarEditorGridPartItem = this._Str_6315(); + // if (((_local_2) && (_local_2.partSet))) + // { + // _local_3 = _local_2.partSet; + // if (((_local_3.isSellable) && (!(k._Str_14439(_local_3.id))))) + // { + // _local_4 = this._Str_8066(0); + // if (((!(_local_4 == null)) && (_local_4.partSet == null))) + // { + // this._Str_8066(1); + // } + // return true; + // } + // } + // return false; + // } + + public get name(): string + { + return this._name; + } + + public get parts(): AvatarEditorGridPartItem[] + { + return this._parts; + } + + public get selectedPartIndex(): number + { + return this._selectedPartIndex; + } +} diff --git a/src/views/avatar-editor/common/HeadModel.ts b/src/views/avatar-editor/common/HeadModel.ts new file mode 100644 index 00000000..1635b79a --- /dev/null +++ b/src/views/avatar-editor/common/HeadModel.ts @@ -0,0 +1,23 @@ +import { AvatarEditorFigureCategory, FigureData } from 'nitro-renderer'; +import { CategoryBaseModel } from './CategoryBaseModel'; + +export class HeadModel extends CategoryBaseModel +{ + public init(): void + { + super.init(); + + this.addCategory(FigureData.HAIR); + this.addCategory(FigureData.HAT); + this.addCategory(FigureData.HEAD_ACCESSORIES); + this.addCategory(FigureData.EYE_ACCESSORIES); + this.addCategory(FigureData.FACE_ACCESSORIES); + + this._isInitalized = true; + } + + public get name(): string + { + return AvatarEditorFigureCategory.HEAD; + } +} diff --git a/src/views/avatar-editor/common/IAvatarEditorCategoryModel.ts b/src/views/avatar-editor/common/IAvatarEditorCategoryModel.ts new file mode 100644 index 00000000..9f6d64e7 --- /dev/null +++ b/src/views/avatar-editor/common/IAvatarEditorCategoryModel.ts @@ -0,0 +1,19 @@ +import { CategoryData } from './CategoryData'; + +export interface IAvatarEditorCategoryModel +{ + init(): void; + dispose(): void; + reset(): void; + getCategoryData(category: string): CategoryData; + selectPart(category: string, partIndex: number): void; + selectColor(category: string, colorIndex: number, paletteId: number): void; + hasClubSelectionsOverLevel(level: number): boolean; + hasInvalidSelectedItems(ownedItems: number[]): boolean; + stripClubItemsOverLevel(level: number): boolean; + stripInvalidSellableItems(): boolean; + categories: Map; + canSetGender: boolean; + maxPaletteCount: number; + name: string; +} diff --git a/src/views/avatar-editor/common/LegModel.ts b/src/views/avatar-editor/common/LegModel.ts new file mode 100644 index 00000000..73b7bafe --- /dev/null +++ b/src/views/avatar-editor/common/LegModel.ts @@ -0,0 +1,21 @@ +import { AvatarEditorFigureCategory, FigureData } from 'nitro-renderer'; +import { CategoryBaseModel } from './CategoryBaseModel'; + +export class LegModel extends CategoryBaseModel +{ + public init(): void + { + super.init(); + + this.addCategory(FigureData.TROUSERS); + this.addCategory(FigureData.SHOES); + this.addCategory(FigureData.TROUSER_ACCESSORIES); + + this._isInitalized = true; + } + + public get name(): string + { + return AvatarEditorFigureCategory.LEGS; + } +} diff --git a/src/views/avatar-editor/common/TorsoModel.ts b/src/views/avatar-editor/common/TorsoModel.ts new file mode 100644 index 00000000..089ca366 --- /dev/null +++ b/src/views/avatar-editor/common/TorsoModel.ts @@ -0,0 +1,22 @@ +import { AvatarEditorFigureCategory, FigureData } from 'nitro-renderer'; +import { CategoryBaseModel } from './CategoryBaseModel'; + +export class TorsoModel extends CategoryBaseModel +{ + public init(): void + { + super.init(); + + this.addCategory(FigureData.SHIRT); + this.addCategory(FigureData.CHEST_PRINTS); + this.addCategory(FigureData.JACKET); + this.addCategory(FigureData.CHEST_ACCESSORIES); + + this._isInitalized = true; + } + + public get name(): string + { + return AvatarEditorFigureCategory.TORSO; + } +} diff --git a/src/views/avatar-editor/views/figure-set-item/AvatarEditorFigureSetItemView.tsx b/src/views/avatar-editor/views/figure-set-item/AvatarEditorFigureSetItemView.tsx new file mode 100644 index 00000000..fcb268cc --- /dev/null +++ b/src/views/avatar-editor/views/figure-set-item/AvatarEditorFigureSetItemView.tsx @@ -0,0 +1,16 @@ +import { FC, useEffect, useState } from 'react'; +import { NitroCardGridItemView } from '../../../../layout/card/grid/item/NitroCardGridItemView'; +import { AvatarEditorFigureSetItemViewProps } from './AvatarEditorFigureSetItemView.types'; + +export const AvatarEditorFigureSetItemView: FC = props => +{ + const { partItem = null, onClick = null } = props; + const [ imageUrl, setImageUrl ] = useState(null); + + useEffect(() => + { + setImageUrl(partItem.imageUrl); + }, [ partItem.imageUrl ]); + + return onClick(partItem) } /> +} diff --git a/src/views/avatar-editor/views/figure-set-item/AvatarEditorFigureSetItemView.types.ts b/src/views/avatar-editor/views/figure-set-item/AvatarEditorFigureSetItemView.types.ts new file mode 100644 index 00000000..92172e9d --- /dev/null +++ b/src/views/avatar-editor/views/figure-set-item/AvatarEditorFigureSetItemView.types.ts @@ -0,0 +1,7 @@ +import { AvatarEditorGridPartItem } from '../../common/AvatarEditorGridPartItem'; + +export interface AvatarEditorFigureSetItemViewProps +{ + partItem: AvatarEditorGridPartItem; + onClick: (item: AvatarEditorGridPartItem) => void; +} diff --git a/src/views/avatar-editor/views/figure-set/AvatarEditorFigureSetView.tsx b/src/views/avatar-editor/views/figure-set/AvatarEditorFigureSetView.tsx new file mode 100644 index 00000000..e16afae6 --- /dev/null +++ b/src/views/avatar-editor/views/figure-set/AvatarEditorFigureSetView.tsx @@ -0,0 +1,28 @@ +import { FC, useCallback } from 'react'; +import { NitroCardGridView } from '../../../../layout/card/grid/NitroCardGridView'; +import { AvatarEditorGridPartItem } from '../../common/AvatarEditorGridPartItem'; +import { AvatarEditorFigureSetItemView } from '../figure-set-item/AvatarEditorFigureSetItemView'; +import { AvatarEditorFigureSetViewProps } from './AvatarEditorFigureSetView.types'; + +export const AvatarEditorFigureSetView: FC = props => +{ + const { model = null, category = null } = props; + + const selectPart = useCallback((part: AvatarEditorGridPartItem) => + { + const index = category.parts.indexOf(part); + + if(index === -1) return; + + model.selectPart(category.name, index); + }, [ model, category ]); + + return ( + + { (category.parts.length > 0) && category.parts.map((item, index) => + { + return ; + }) } + + ) +} diff --git a/src/views/avatar-editor/views/figure-set/AvatarEditorFigureSetView.types.ts b/src/views/avatar-editor/views/figure-set/AvatarEditorFigureSetView.types.ts new file mode 100644 index 00000000..b44992be --- /dev/null +++ b/src/views/avatar-editor/views/figure-set/AvatarEditorFigureSetView.types.ts @@ -0,0 +1,8 @@ +import { CategoryData } from '../../common/CategoryData'; +import { IAvatarEditorCategoryModel } from '../../common/IAvatarEditorCategoryModel'; + +export interface AvatarEditorFigureSetViewProps +{ + model: IAvatarEditorCategoryModel; + category: CategoryData; +} diff --git a/src/views/avatar-editor/views/model/AvatarEditorModelView.tsx b/src/views/avatar-editor/views/model/AvatarEditorModelView.tsx new file mode 100644 index 00000000..4dfc58d0 --- /dev/null +++ b/src/views/avatar-editor/views/model/AvatarEditorModelView.tsx @@ -0,0 +1,60 @@ +import { FC, useCallback, useEffect, useState } from 'react'; +import { CategoryData } from '../../common/CategoryData'; +import { AvatarEditorFigureSetView } from '../figure-set/AvatarEditorFigureSetView'; +import { AvatarEditorModelViewProps } from './AvatarEditorModelView.types'; + +export const AvatarEditorModelView: FC = props => +{ + const { model = null, editor = null } = props; + const [ activeCategory, setActiveCategory ] = useState(null); + + const selectGender = useCallback((gender: string) => + { + editor.gender = gender; + }, [ editor ]); + + const selectCategory = useCallback((name: string) => + { + const category = model.categories.get(name); + + if(!category) return; + + category.init(); + + setActiveCategory(category); + + for(const part of category.parts) + { + if(!part || !part.isSelected) continue; + + model.maxPaletteCount = part.colorLayerCount; + + break; + } + }, [ model ]); + + useEffect(() => + { + model.init(); + + for(const name of model.categories.keys()) + { + selectCategory(name); + + break; + } + }, [ model, selectCategory ]); + + if(!activeCategory) return null; + + return ( +
+
+
+ +
+
+
+
+ ); +} diff --git a/src/views/avatar-editor/views/model/AvatarEditorModelView.types.ts b/src/views/avatar-editor/views/model/AvatarEditorModelView.types.ts new file mode 100644 index 00000000..322e9a86 --- /dev/null +++ b/src/views/avatar-editor/views/model/AvatarEditorModelView.types.ts @@ -0,0 +1,8 @@ +import { AvatarEditor } from '../../common/AvatarEditor'; +import { IAvatarEditorCategoryModel } from '../../common/IAvatarEditorCategoryModel'; + +export interface AvatarEditorModelViewProps +{ + model: IAvatarEditorCategoryModel; + editor: AvatarEditor; +}