diff --git a/src/api/avatar/AvatarEditorThumbnailsHelper.ts b/src/api/avatar/AvatarEditorThumbnailsHelper.ts index f9ce18f..bffc418 100644 --- a/src/api/avatar/AvatarEditorThumbnailsHelper.ts +++ b/src/api/avatar/AvatarEditorThumbnailsHelper.ts @@ -109,22 +109,6 @@ export class AvatarEditorThumbnailsHelper container.addChild(sprite); } - if(container.children.length > 0) - { - const isPetPart = parts.some(p => p.type === 'pt' || p.type === 'ptl' || p.type === 'ptr'); - - if(isPetPart) - { - const bounds = container.getBounds(); - - for(const child of container.children) - { - (child as NitroSprite).position.x -= bounds.x; - (child as NitroSprite).position.y -= bounds.y; - } - } - } - return container; }; @@ -133,9 +117,9 @@ export class AvatarEditorThumbnailsHelper const resetFigure = async (figure: string) => { const container = buildContainer(part, useColors, partColors, isDisabled); - const imageUrl = await TextureUtils.generateImageUrl(container); + const imageUrl = await TextureUtils.generateImageUrl({ target: container, resolution: 1 }); - AvatarEditorThumbnailsHelper.THUMBNAIL_CACHE.set(thumbnailKey, imageUrl); + if(imageUrl) AvatarEditorThumbnailsHelper.THUMBNAIL_CACHE.set(thumbnailKey, imageUrl); resolve(imageUrl); }; diff --git a/src/components/MainView.tsx b/src/components/MainView.tsx index 4a39a59..b03d21d 100644 --- a/src/components/MainView.tsx +++ b/src/components/MainView.tsx @@ -9,7 +9,6 @@ import { CampaignView } from './campaign/CampaignView'; import { CatalogView } from './catalog/CatalogView'; import { ChatHistoryView } from './chat-history/ChatHistoryView'; import { FloorplanEditorView } from './floorplan-editor/FloorplanEditorView'; -import { FurniEditorView } from './furni-editor/FurniEditorView'; import { FriendsView } from './friends/FriendsView'; import { GameCenterView } from './game-center/GameCenterView'; import { GroupsView } from './groups/GroupsView'; @@ -121,7 +120,6 @@ export const MainView: FC<{}> = props => - diff --git a/src/components/avatar-editor/figure-set/AvatarEditorFigureSetItemView.tsx b/src/components/avatar-editor/figure-set/AvatarEditorFigureSetItemView.tsx index a08321d..789dc74 100644 --- a/src/components/avatar-editor/figure-set/AvatarEditorFigureSetItemView.tsx +++ b/src/components/avatar-editor/figure-set/AvatarEditorFigureSetItemView.tsx @@ -1,4 +1,3 @@ -import { AvatarEditorFigureCategory, AvatarFigurePartType } from '@nitrots/nitro-renderer'; import { FC, useEffect, useState } from 'react'; import { AvatarEditorThumbnailsHelper, GetClubMemberLevel, GetConfigurationValue, IAvatarEditorCategoryPartItem } from '../../../api'; import { LayoutCurrencyIcon, LayoutGridItemProps } from '../../../common'; @@ -15,7 +14,7 @@ export const AvatarEditorFigureSetItemView: FC<{ { const { setType = null, partItem = null, isSelected = false, width = '100%', ...rest } = props; const [ assetUrl, setAssetUrl ] = useState(''); - const { activeModelKey = '', selectedColorParts = null, getFigureStringWithFace = null } = useAvatarEditor(); + const { selectedColorParts = null, getFigureStringWithFace = null } = useAvatarEditor(); const clubLevel = partItem.partSet?.clubLevel ?? 0; const isHC = !GetConfigurationValue('hc.disabled', false) && (clubLevel > 0); @@ -24,7 +23,9 @@ export const AvatarEditorFigureSetItemView: FC<{ useEffect(() => { - if(!setType || !setType.length || !partItem) return; + setAssetUrl(''); + + if(!setType || !setType.length || !partItem || partItem.isClear) return; const loadImage = async () => { @@ -34,7 +35,7 @@ export const AvatarEditorFigureSetItemView: FC<{ let url: string = null; - if(setType === AvatarFigurePartType.HEAD && activeModelKey !== AvatarEditorFigureCategory.NFT) + if(setType === 'hd') { url = await AvatarEditorThumbnailsHelper.buildForFace(getFigureStringWithFace(partItem.id), partIsLocked || isSellableNotOwned); } @@ -53,12 +54,27 @@ export const AvatarEditorFigureSetItemView: FC<{ }; loadImage(); - }, [ setType, partItem, selectedColorParts, getFigureStringWithFace, isSellableNotOwned, activeModelKey ]); + }, [ setType, partItem, selectedColorParts, getFigureStringWithFace, isSellableNotOwned ]); if(!partItem) return null; + const isHead = (setType === 'hd'); + return ( - + + { !partItem.isClear && assetUrl && !isHead && + } { !partItem.isClear && isHC && } { partItem.isClear && } { !partItem.isClear && partItem.partSet.isSellable && !isSellableNotOwned && } diff --git a/src/components/avatar-editor/figure-set/AvatarEditorFigureSetView.tsx b/src/components/avatar-editor/figure-set/AvatarEditorFigureSetView.tsx index 7d7bd68..179f894 100644 --- a/src/components/avatar-editor/figure-set/AvatarEditorFigureSetView.tsx +++ b/src/components/avatar-editor/figure-set/AvatarEditorFigureSetView.tsx @@ -7,9 +7,10 @@ import { AvatarEditorFigureSetItemView } from './AvatarEditorFigureSetItemView'; export const AvatarEditorFigureSetView: FC<{ category: IAvatarEditorCategory; columnCount: number; + estimateSize?: number; }> = props => { - const { category = null, columnCount = 3 } = props; + const { category = null, columnCount = 3, estimateSize = 50 } = props; const { selectedParts = null, selectEditorPart } = useAvatarEditor(); const isPartItemSelected = (partItem: IAvatarEditorCategoryPartItem) => @@ -29,7 +30,7 @@ export const AvatarEditorFigureSetView: FC<{ }; return ( - columnCount={ columnCount } estimateSize={ 50 } itemRender={ (item: IAvatarEditorCategoryPartItem) => + columnCount={ columnCount } estimateSize={ estimateSize } itemRender={ (item: IAvatarEditorCategoryPartItem) => { if(!item) return null; diff --git a/src/components/room/widgets/avatar-info/infostand/InfoStandWidgetFurniView.tsx b/src/components/room/widgets/avatar-info/infostand/InfoStandWidgetFurniView.tsx index 48e9b84..98604a6 100644 --- a/src/components/room/widgets/avatar-info/infostand/InfoStandWidgetFurniView.tsx +++ b/src/components/room/widgets/avatar-info/infostand/InfoStandWidgetFurniView.tsx @@ -574,19 +574,6 @@ export const InfoStandWidgetFurniView: FC = props onClick={ () => setDropdownOpen(!dropdownOpen) }> { dropdownOpen ? `${LocalizeText('widget.furni.present.close')} Buildtools` : `${LocalizeText('navigator.roomsettings.doormode.open')} Buildtools` } - { dropdownOpen &&
{ /* Left panel: position + rotation */ } diff --git a/src/components/toolbar/ToolbarView.tsx b/src/components/toolbar/ToolbarView.tsx index fbb5ae8..ea9fb16 100644 --- a/src/components/toolbar/ToolbarView.tsx +++ b/src/components/toolbar/ToolbarView.tsx @@ -96,8 +96,6 @@ export const ToolbarView: FC<{ isInRoom: boolean }> = props => CreateLinkEvent('camera/toggle') } /> } { isMod && CreateLinkEvent('mod-tools/toggle') } /> } - { isMod && - CreateLinkEvent('furni-editor/toggle') } /> } diff --git a/src/hooks/furni-editor/index.ts b/src/hooks/furni-editor/index.ts deleted file mode 100644 index 47ce6ef..0000000 --- a/src/hooks/furni-editor/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './useFurniEditor'; diff --git a/src/hooks/furni-editor/useFurniEditor.ts b/src/hooks/furni-editor/useFurniEditor.ts deleted file mode 100644 index e4258e5..0000000 --- a/src/hooks/furni-editor/useFurniEditor.ts +++ /dev/null @@ -1,239 +0,0 @@ -import { useCallback, useState } from 'react'; - -export interface FurniItem -{ - id: number; - spriteId: number; - itemName: string; - publicName: string; - type: string; - width: number; - length: number; - stackHeight: number; - allowStack: boolean; - allowWalk: boolean; - allowSit: boolean; - allowLay: boolean; - interactionType: string; - interactionModesCount: number; -} - -export interface FurniDetail extends FurniItem -{ - allowGift: boolean; - allowTrade: boolean; - allowRecycle: boolean; - allowMarketplaceSell: boolean; - allowInventoryStack: boolean; - vendingIds: string; - customparams: string; - effectIdMale: number; - effectIdFemale: number; - clothingOnWalk: string; - multiheight: string; - description: string; - usageCount: number; -} - -export interface CatalogRef -{ - id: number; - catalogName: string; - costCredits: number; - costPoints: number; - pointsType: number; - pageId: number; - pageName: string; -} - -const API_BASE = '/api/admin/furni-editor'; - -async function apiFetch(url: string, options?: RequestInit): Promise -{ - const res = await fetch(url, { credentials: 'include', ...options }); - const data = await res.json(); - - if(!res.ok || data.error) throw new Error(data.error || 'API error'); - - return data; -} - -export const useFurniEditor = () => -{ - const [ items, setItems ] = useState([]); - const [ total, setTotal ] = useState(0); - const [ page, setPage ] = useState(1); - const [ loading, setLoading ] = useState(false); - const [ error, setError ] = useState(null); - const [ selectedItem, setSelectedItem ] = useState(null); - const [ catalogItems, setCatalogItems ] = useState([]); - const [ interactions, setInteractions ] = useState([]); - const [ furniDataEntry, setFurniDataEntry ] = useState | null>(null); - - const clearError = useCallback(() => setError(null), []); - - const searchItems = useCallback(async (query: string, type: string, pg: number) => - { - setLoading(true); - setError(null); - - try - { - const params = new URLSearchParams({ q: query, limit: '20', page: String(pg) }); - - if(type) params.set('type', type); - - const data = await apiFetch<{ items: FurniItem[]; total: number; page: number }>(`${ API_BASE }?${ params }`); - - setItems(data.items); - setTotal(data.total); - setPage(data.page); - } - catch(e: any) - { - setError(e.message); - } - finally - { - setLoading(false); - } - }, []); - - const loadDetail = useCallback(async (id: number): Promise => - { - setLoading(true); - setError(null); - - try - { - const data = await apiFetch<{ item: FurniDetail; catalogItems: CatalogRef[]; furniDataEntry: Record | null }>(`${ API_BASE }/detail?id=${ id }`); - - setSelectedItem(data.item); - setCatalogItems(data.catalogItems); - setFurniDataEntry(data.furniDataEntry); - - return true; - } - catch(e: any) - { - setError(e.message); - - return false; - } - finally - { - setLoading(false); - } - }, []); - - const updateItem = useCallback(async (id: number, fields: Record) => - { - setLoading(true); - setError(null); - - try - { - await apiFetch(`${ API_BASE }/update?id=${ id }`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(fields) - }); - - return true; - } - catch(e: any) - { - setError(e.message); - - return false; - } - finally - { - setLoading(false); - } - }, []); - - const createItem = useCallback(async (fields: Record) => - { - setLoading(true); - setError(null); - - try - { - const data = await apiFetch<{ id: number }>(`${ API_BASE }`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(fields) - }); - - return data.id; - } - catch(e: any) - { - setError(e.message); - - return null; - } - finally - { - setLoading(false); - } - }, []); - - const deleteItem = useCallback(async (id: number) => - { - setLoading(true); - setError(null); - - try - { - await apiFetch(`${ API_BASE }/delete?id=${ id }`, { method: 'POST' }); - - return true; - } - catch(e: any) - { - setError(e.message); - - return false; - } - finally - { - setLoading(false); - } - }, []); - - const loadInteractions = useCallback(async () => - { - try - { - const data = await apiFetch<{ interactions: Array }>(`${ API_BASE }/interactions`); - - setInteractions(data.interactions.map(i => typeof i === 'string' ? i : i.name)); - } - catch {} - }, []); - - const loadBySpriteId = useCallback(async (spriteId: number): Promise => - { - try - { - const data = await apiFetch<{ id: number }>(`${ API_BASE }/by-sprite?spriteId=${ spriteId }`); - - return await loadDetail(data.id); - } - catch(e: any) - { - setError(e.message); - - return false; - } - }, [ loadDetail ]); - - return { - items, total, page, loading, error, clearError, - selectedItem, setSelectedItem, catalogItems, furniDataEntry, - interactions, - searchItems, loadDetail, loadBySpriteId, updateItem, createItem, deleteItem, loadInteractions - }; -};