mirror of
https://github.com/duckietm/Nitro-V3.git
synced 2026-06-20 07:26:19 +00:00
🆙 Init V3
This commit is contained in:
@@ -0,0 +1,160 @@
|
||||
import { NitroCard } from '@layout/NitroCard';
|
||||
import { AddLinkEventTracker, BadgePointLimitsEvent, GetLocalizationManager, GetRoomEngine, ILinkEventTracker, IRoomSession, RemoveLinkEventTracker, RoomEngineObjectEvent, RoomEngineObjectPlacedEvent, RoomPreviewer, RoomSessionEvent } from '@nitrots/nitro-renderer';
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import { LocalizeText, UnseenItemCategory, isObjectMoverRequested, setObjectMoverRequested } from '../../api';
|
||||
import { useInventoryTrade, useInventoryUnseenTracker, useMessageEvent, useNitroEvent } from '../../hooks';
|
||||
import { InventoryBadgeView } from './views/badge/InventoryBadgeView';
|
||||
import { InventoryBotView } from './views/bot/InventoryBotView';
|
||||
import { InventoryFurnitureView } from './views/furniture/InventoryFurnitureView';
|
||||
import { InventoryTradeView } from './views/furniture/InventoryTradeView';
|
||||
import { InventoryPetView } from './views/pet/InventoryPetView';
|
||||
|
||||
const TAB_FURNITURE: string = 'inventory.furni';
|
||||
const TAB_BOTS: string = 'inventory.bots';
|
||||
const TAB_PETS: string = 'inventory.furni.tab.pets';
|
||||
const TAB_BADGES: string = 'inventory.badges';
|
||||
const TABS = [ TAB_FURNITURE, TAB_BOTS, TAB_PETS, TAB_BADGES ];
|
||||
const UNSEEN_CATEGORIES = [ UnseenItemCategory.FURNI, UnseenItemCategory.BOT, UnseenItemCategory.PET, UnseenItemCategory.BADGE ];
|
||||
|
||||
export const InventoryView: FC<{}> = props =>
|
||||
{
|
||||
const [ isVisible, setIsVisible ] = useState(false);
|
||||
const [ currentTab, setCurrentTab ] = useState<string>(TABS[0]);
|
||||
const [ roomSession, setRoomSession ] = useState<IRoomSession>(null);
|
||||
const [ roomPreviewer, setRoomPreviewer ] = useState<RoomPreviewer>(null);
|
||||
const { isTrading = false, stopTrading = null } = useInventoryTrade();
|
||||
const { getCount = null, resetCategory = null } = useInventoryUnseenTracker();
|
||||
|
||||
const onClose = () =>
|
||||
{
|
||||
if(isTrading) stopTrading();
|
||||
|
||||
setIsVisible(false);
|
||||
};
|
||||
|
||||
useNitroEvent<RoomEngineObjectPlacedEvent>(RoomEngineObjectEvent.PLACED, event =>
|
||||
{
|
||||
if(!isObjectMoverRequested()) return;
|
||||
|
||||
setObjectMoverRequested(false);
|
||||
|
||||
if(!event.placedInRoom) setIsVisible(true);
|
||||
});
|
||||
|
||||
useNitroEvent<RoomSessionEvent>([
|
||||
RoomSessionEvent.CREATED,
|
||||
RoomSessionEvent.ENDED
|
||||
], event =>
|
||||
{
|
||||
switch(event.type)
|
||||
{
|
||||
case RoomSessionEvent.CREATED:
|
||||
setRoomSession(event.session);
|
||||
return;
|
||||
case RoomSessionEvent.ENDED:
|
||||
setRoomSession(null);
|
||||
setIsVisible(false);
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
useMessageEvent<BadgePointLimitsEvent>(BadgePointLimitsEvent, event =>
|
||||
{
|
||||
const parser = event.getParser();
|
||||
|
||||
for(const data of parser.data) GetLocalizationManager().setBadgePointLimit(data.badgeId, data.limit);
|
||||
});
|
||||
|
||||
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: 'inventory/'
|
||||
};
|
||||
|
||||
AddLinkEventTracker(linkTracker);
|
||||
|
||||
return () => RemoveLinkEventTracker(linkTracker);
|
||||
}, []);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
setRoomPreviewer(new RoomPreviewer(GetRoomEngine(), ++RoomPreviewer.PREVIEW_COUNTER));
|
||||
|
||||
return () =>
|
||||
{
|
||||
setRoomPreviewer(prevValue =>
|
||||
{
|
||||
prevValue.dispose();
|
||||
|
||||
return null;
|
||||
});
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!isVisible && isTrading) setIsVisible(true);
|
||||
}, [ isVisible, isTrading ]);
|
||||
|
||||
if(!isVisible) return null;
|
||||
|
||||
return (
|
||||
<NitroCard
|
||||
className="w-inventory-w h-inventory-h min-w-inventory-w min-h-inventory-h"
|
||||
uniqueKey="inventory">
|
||||
<NitroCard.Header
|
||||
headerText={ LocalizeText('inventory.title') }
|
||||
onCloseClick={ onClose } />
|
||||
{ !isTrading &&
|
||||
<>
|
||||
<NitroCard.Tabs>
|
||||
{ TABS.map((name, index) =>
|
||||
{
|
||||
return (
|
||||
<NitroCard.TabItem
|
||||
key={ index }
|
||||
count={ getCount(UNSEEN_CATEGORIES[index]) }
|
||||
isActive={ (currentTab === name) }
|
||||
onClick={ event => setCurrentTab(name) }>
|
||||
{ LocalizeText(name) }
|
||||
</NitroCard.TabItem>
|
||||
);
|
||||
}) }
|
||||
</NitroCard.Tabs>
|
||||
<NitroCard.Content>
|
||||
{ (currentTab === TAB_FURNITURE ) &&
|
||||
<InventoryFurnitureView roomPreviewer={ roomPreviewer } roomSession={ roomSession } /> }
|
||||
{ (currentTab === TAB_BOTS ) &&
|
||||
<InventoryBotView roomPreviewer={ roomPreviewer } roomSession={ roomSession } /> }
|
||||
{ (currentTab === TAB_PETS ) &&
|
||||
<InventoryPetView roomPreviewer={ roomPreviewer } roomSession={ roomSession } /> }
|
||||
{ (currentTab === TAB_BADGES ) &&
|
||||
<InventoryBadgeView /> }
|
||||
</NitroCard.Content>
|
||||
</> }
|
||||
{ isTrading &&
|
||||
<NitroCard.Content>
|
||||
<InventoryTradeView cancelTrade={ onClose } />
|
||||
</NitroCard.Content> }
|
||||
</NitroCard>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,26 @@
|
||||
import { FC } from 'react';
|
||||
import { Column, Grid, GridProps, Text } from '../../../common';
|
||||
|
||||
export interface InventoryCategoryEmptyViewProps extends GridProps
|
||||
{
|
||||
title: string;
|
||||
desc: string;
|
||||
}
|
||||
|
||||
export const InventoryCategoryEmptyView: FC<InventoryCategoryEmptyViewProps> = props =>
|
||||
{
|
||||
const { title = '', desc = '', children = null, ...rest } = props;
|
||||
|
||||
return (
|
||||
<Grid { ...rest }>
|
||||
<Column center overflow="hidden" size={ 5 }>
|
||||
<div className="empty-image" />
|
||||
</Column>
|
||||
<Column justifyContent="center" overflow="hidden" size={ 7 }>
|
||||
<Text truncate fontSize={ 5 } fontWeight="bold" overflow="unset">{ title }</Text>
|
||||
<Text overflow="auto">{ desc }</Text>
|
||||
</Column>
|
||||
{ children }
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,20 @@
|
||||
import { FC, PropsWithChildren } from 'react';
|
||||
import { UnseenItemCategory } from '../../../../api';
|
||||
import { LayoutBadgeImageView } from '../../../../common';
|
||||
import { useInventoryBadges, useInventoryUnseenTracker } from '../../../../hooks';
|
||||
import { InfiniteGrid } from '../../../../layout';
|
||||
|
||||
export const InventoryBadgeItemView: FC<PropsWithChildren<{ badgeCode: string }>> = props =>
|
||||
{
|
||||
const { badgeCode = null, children = null, ...rest } = props;
|
||||
const { selectedBadgeCode = null, setSelectedBadgeCode = null, toggleBadge = null, getBadgeId = null } = useInventoryBadges();
|
||||
const { isUnseen = null } = useInventoryUnseenTracker();
|
||||
const unseen = isUnseen(UnseenItemCategory.BADGE, getBadgeId(badgeCode));
|
||||
|
||||
return (
|
||||
<InfiniteGrid.Item itemActive={ (selectedBadgeCode === badgeCode) } itemUnseen={ unseen } onDoubleClick={ event => toggleBadge(selectedBadgeCode) } onMouseDown={ event => setSelectedBadgeCode(badgeCode) } { ...rest }>
|
||||
<LayoutBadgeImageView badgeCode={ badgeCode } />
|
||||
{ children }
|
||||
</InfiniteGrid.Item>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,70 @@
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import { LocalizeBadgeName, LocalizeText, UnseenItemCategory } from '../../../../api';
|
||||
import { LayoutBadgeImageView } from '../../../../common';
|
||||
import { useInventoryBadges, useInventoryUnseenTracker } from '../../../../hooks';
|
||||
import { InfiniteGrid, NitroButton } from '../../../../layout';
|
||||
import { InventoryBadgeItemView } from './InventoryBadgeItemView';
|
||||
|
||||
export const InventoryBadgeView: FC<{}> = props =>
|
||||
{
|
||||
const [ isVisible, setIsVisible ] = useState(false);
|
||||
const { badgeCodes = [], activeBadgeCodes = [], selectedBadgeCode = null, isWearingBadge = null, canWearBadges = null, toggleBadge = null, getBadgeId = null, activate = null, deactivate = null } = useInventoryBadges();
|
||||
const { isUnseen = null, removeUnseen = null } = useInventoryUnseenTracker();
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!selectedBadgeCode || !isUnseen(UnseenItemCategory.BADGE, getBadgeId(selectedBadgeCode))) return;
|
||||
|
||||
removeUnseen(UnseenItemCategory.BADGE, getBadgeId(selectedBadgeCode));
|
||||
}, [ selectedBadgeCode, isUnseen, removeUnseen, getBadgeId ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!isVisible) return;
|
||||
|
||||
const id = activate();
|
||||
|
||||
return () => deactivate(id);
|
||||
}, [ isVisible, activate, deactivate ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
setIsVisible(true);
|
||||
|
||||
return () => setIsVisible(false);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="grid h-full grid-cols-12 gap-2">
|
||||
<div className="flex flex-col col-span-7 gap-1 overflow-hidden">
|
||||
<InfiniteGrid<string>
|
||||
columnCount={ 5 }
|
||||
estimateSize={ 50 }
|
||||
itemRender={ item => <InventoryBadgeItemView badgeCode={ item } /> }
|
||||
items={ badgeCodes.filter(code => !isWearingBadge(code)) } />
|
||||
</div>
|
||||
<div className="flex flex-col justify-between col-span-5 overflow-auto">
|
||||
<div className="flex flex-col gap-2 overflow-hidden">
|
||||
<span className="text-sm truncate grow">{ LocalizeText('inventory.badges.activebadges') }</span>
|
||||
<InfiniteGrid<string>
|
||||
columnCount={ 3 }
|
||||
estimateSize={ 50 }
|
||||
itemRender={ item => <InventoryBadgeItemView badgeCode={ item } /> }
|
||||
items={ activeBadgeCodes } />
|
||||
</div>
|
||||
{ !!selectedBadgeCode &&
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="items-center gap-2">
|
||||
<LayoutBadgeImageView shrink badgeCode={ selectedBadgeCode } />
|
||||
<span className="text-sm truncate grow">{ LocalizeBadgeName(selectedBadgeCode) }</span>
|
||||
</div>
|
||||
<NitroButton
|
||||
disabled={ !isWearingBadge(selectedBadgeCode) && !canWearBadges() }
|
||||
onClick={ event => toggleBadge(selectedBadgeCode) }>
|
||||
{ LocalizeText(isWearingBadge(selectedBadgeCode) ? 'inventory.badges.clearbadge' : 'inventory.badges.wearbadge') }
|
||||
</NitroButton>
|
||||
</div> }
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,46 @@
|
||||
import { MouseEventType } from '@nitrots/nitro-renderer';
|
||||
import { FC, MouseEvent, PropsWithChildren, useState } from 'react';
|
||||
import { IBotItem, UnseenItemCategory, attemptBotPlacement } from '../../../../api';
|
||||
import { LayoutAvatarImageView } from '../../../../common';
|
||||
import { useInventoryBots, useInventoryUnseenTracker } from '../../../../hooks';
|
||||
import { InfiniteGrid } from '../../../../layout';
|
||||
|
||||
export const InventoryBotItemView: FC<PropsWithChildren<{
|
||||
botItem: IBotItem
|
||||
}>> = props =>
|
||||
{
|
||||
const { botItem = null, children = null, ...rest } = props;
|
||||
const [ isMouseDown, setMouseDown ] = useState(false);
|
||||
const { selectedBot = null, setSelectedBot = null } = useInventoryBots();
|
||||
const { isUnseen = null } = useInventoryUnseenTracker();
|
||||
const unseen = isUnseen(UnseenItemCategory.BOT, botItem.botData.id);
|
||||
|
||||
const onMouseEvent = (event: MouseEvent) =>
|
||||
{
|
||||
switch(event.type)
|
||||
{
|
||||
case MouseEventType.MOUSE_DOWN:
|
||||
setSelectedBot(botItem);
|
||||
setMouseDown(true);
|
||||
return;
|
||||
case MouseEventType.MOUSE_UP:
|
||||
setMouseDown(false);
|
||||
return;
|
||||
case MouseEventType.ROLL_OUT:
|
||||
if(!isMouseDown || (selectedBot !== botItem)) return;
|
||||
|
||||
attemptBotPlacement(botItem);
|
||||
return;
|
||||
case 'dblclick':
|
||||
attemptBotPlacement(botItem);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<InfiniteGrid.Item itemActive={ (selectedBot === botItem) } itemUnseen={ unseen } onDoubleClick={ onMouseEvent } onMouseDown={ onMouseEvent } onMouseOut={ onMouseEvent } onMouseUp={ onMouseEvent } { ...rest } className="*:[background-position-y:-32px]">
|
||||
<LayoutAvatarImageView direction={ 3 } figure={ botItem.botData.figure } headOnly={ true } />
|
||||
{ children }
|
||||
</InfiniteGrid.Item>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,90 @@
|
||||
import { GetRoomEngine, IRoomSession, RoomObjectVariable, RoomPreviewer } from '@nitrots/nitro-renderer';
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import { IBotItem, LocalizeText, UnseenItemCategory, attemptBotPlacement } from '../../../../api';
|
||||
import { LayoutRoomPreviewerView } from '../../../../common';
|
||||
import { useInventoryBots, useInventoryUnseenTracker } from '../../../../hooks';
|
||||
import { InfiniteGrid, NitroButton } from '../../../../layout';
|
||||
import { InventoryCategoryEmptyView } from '../InventoryCategoryEmptyView';
|
||||
import { InventoryBotItemView } from './InventoryBotItemView';
|
||||
|
||||
export const InventoryBotView: FC<{
|
||||
roomSession: IRoomSession;
|
||||
roomPreviewer: RoomPreviewer;
|
||||
}> = props =>
|
||||
{
|
||||
const { roomSession = null, roomPreviewer = null } = props;
|
||||
const [ isVisible, setIsVisible ] = useState(false);
|
||||
const { botItems = [], selectedBot = null, activate = null, deactivate = null } = useInventoryBots();
|
||||
const { isUnseen = null, removeUnseen = null } = useInventoryUnseenTracker();
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!selectedBot || !roomPreviewer) return;
|
||||
|
||||
const botData = selectedBot.botData;
|
||||
|
||||
const roomEngine = GetRoomEngine();
|
||||
|
||||
let wallType = roomEngine.getRoomInstanceVariable<string>(roomEngine.activeRoomId, RoomObjectVariable.ROOM_WALL_TYPE);
|
||||
let floorType = roomEngine.getRoomInstanceVariable<string>(roomEngine.activeRoomId, RoomObjectVariable.ROOM_FLOOR_TYPE);
|
||||
let landscapeType = roomEngine.getRoomInstanceVariable<string>(roomEngine.activeRoomId, RoomObjectVariable.ROOM_LANDSCAPE_TYPE);
|
||||
|
||||
wallType = (wallType && wallType.length) ? wallType : '101';
|
||||
floorType = (floorType && floorType.length) ? floorType : '101';
|
||||
landscapeType = (landscapeType && landscapeType.length) ? landscapeType : '1.1';
|
||||
|
||||
roomPreviewer.reset(false);
|
||||
roomPreviewer.updateRoomWallsAndFloorVisibility(true, true);
|
||||
roomPreviewer.updateObjectRoom(floorType, wallType, landscapeType);
|
||||
roomPreviewer.addAvatarIntoRoom(botData.figure, 0);
|
||||
}, [ roomPreviewer, selectedBot ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!selectedBot || !isUnseen(UnseenItemCategory.BOT, selectedBot.botData.id)) return;
|
||||
|
||||
removeUnseen(UnseenItemCategory.BOT, selectedBot.botData.id);
|
||||
}, [ selectedBot, isUnseen, removeUnseen ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!isVisible) return;
|
||||
|
||||
const id = activate();
|
||||
|
||||
return () => deactivate(id);
|
||||
}, [ isVisible, activate, deactivate ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
setIsVisible(true);
|
||||
|
||||
return () => setIsVisible(false);
|
||||
}, []);
|
||||
|
||||
if(!botItems || !botItems.length) return <InventoryCategoryEmptyView desc={ LocalizeText('inventory.empty.bots.desc') } title={ LocalizeText('inventory.empty.bots.title') } />;
|
||||
|
||||
return (
|
||||
<div className="grid h-full grid-cols-12 gap-2">
|
||||
<div className="flex flex-col col-span-7 gap-1 overflow-hidden">
|
||||
<InfiniteGrid<IBotItem>
|
||||
columnCount={ 6 }
|
||||
itemRender={ item => <InventoryBotItemView botItem={ item } /> }
|
||||
items={ botItems } />
|
||||
</div>
|
||||
<div className="flex flex-col col-span-5">
|
||||
<div className="relative flex flex-col">
|
||||
<LayoutRoomPreviewerView height={ 140 } roomPreviewer={ roomPreviewer } />
|
||||
</div>
|
||||
{ selectedBot &&
|
||||
<div className="flex flex-col justify-between gap-2 grow">
|
||||
<span className="truncate grow">{ selectedBot.botData.name }</span>
|
||||
{ !!roomSession &&
|
||||
<NitroButton onClick={ event => attemptBotPlacement(selectedBot) }>
|
||||
{ LocalizeText('inventory.furni.placetoroom') }
|
||||
</NitroButton> }
|
||||
</div> }
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,40 @@
|
||||
import { MouseEventType } from '@nitrots/nitro-renderer';
|
||||
import { FC, MouseEvent, useState } from 'react';
|
||||
import { GroupItem, attemptItemPlacement } from '../../../../api';
|
||||
import { useInventoryFurni } from '../../../../hooks';
|
||||
import { InfiniteGrid, classNames } from '../../../../layout';
|
||||
|
||||
export const InventoryFurnitureItemView: FC<{
|
||||
groupItem: GroupItem
|
||||
}> = props =>
|
||||
{
|
||||
const { groupItem = null, ...rest } = props;
|
||||
const [ isMouseDown, setMouseDown ] = useState(false);
|
||||
const { selectedItem = null, setSelectedItem = null } = useInventoryFurni();
|
||||
|
||||
const onMouseEvent = (event: MouseEvent) =>
|
||||
{
|
||||
switch(event.type)
|
||||
{
|
||||
case MouseEventType.MOUSE_DOWN:
|
||||
setSelectedItem(groupItem);
|
||||
setMouseDown(true);
|
||||
return;
|
||||
case MouseEventType.MOUSE_UP:
|
||||
setMouseDown(false);
|
||||
return;
|
||||
case MouseEventType.ROLL_OUT:
|
||||
if(!isMouseDown || !(groupItem === selectedItem)) return;
|
||||
|
||||
attemptItemPlacement(groupItem);
|
||||
return;
|
||||
case 'dblclick':
|
||||
attemptItemPlacement(groupItem);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
const count = groupItem.getUnlockedCount();
|
||||
|
||||
return <InfiniteGrid.Item className={ classNames(!count && 'opacity-50') } itemActive={ (groupItem === selectedItem) } itemCount={ groupItem.getUnlockedCount() } itemImage={ groupItem.iconUrl } itemUniqueNumber={ groupItem.stuffData.uniqueNumber } itemUnseen={ groupItem.hasUnseenItems } onDoubleClick={ onMouseEvent } onMouseDown={ onMouseEvent } onMouseOut={ onMouseEvent } onMouseUp={ onMouseEvent } />;
|
||||
};
|
||||
@@ -0,0 +1,47 @@
|
||||
import { Dispatch, FC, SetStateAction, useEffect, useState } from 'react';
|
||||
import { FaSearch } from 'react-icons/fa';
|
||||
import { GroupItem, LocalizeText } from '../../../../api';
|
||||
import { NitroButton, NitroInput } from '../../../../layout';
|
||||
|
||||
export const InventoryFurnitureSearchView: FC<{
|
||||
groupItems: GroupItem[];
|
||||
setGroupItems: Dispatch<SetStateAction<GroupItem[]>>;
|
||||
}> = props =>
|
||||
{
|
||||
const { groupItems = [], setGroupItems = null } = props;
|
||||
const [ searchValue, setSearchValue ] = useState('');
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
let filteredGroupItems = [ ...groupItems ];
|
||||
|
||||
if(searchValue && searchValue.length)
|
||||
{
|
||||
const comparison = searchValue.toLocaleLowerCase();
|
||||
|
||||
filteredGroupItems = groupItems.filter(item =>
|
||||
{
|
||||
if(comparison && comparison.length)
|
||||
{
|
||||
if(item.name.toLocaleLowerCase().includes(comparison)) return item;
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
setGroupItems(filteredGroupItems);
|
||||
}, [ groupItems, setGroupItems, searchValue ]);
|
||||
|
||||
return (
|
||||
<div className="flex gap-1">
|
||||
<NitroInput
|
||||
placeholder={ LocalizeText('generic.search') }
|
||||
value={ searchValue }
|
||||
onChange={ event => setSearchValue(event.target.value) } />
|
||||
<NitroButton>
|
||||
<FaSearch className="fa-icon" />
|
||||
</NitroButton>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,146 @@
|
||||
import { InfiniteGrid } from '@layout/InfiniteGrid';
|
||||
import { GetRoomEngine, GetSessionDataManager, IRoomSession, RoomObjectVariable, RoomPreviewer, Vector3d } from '@nitrots/nitro-renderer';
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import { DispatchUiEvent, FurniCategory, GroupItem, LocalizeText, UnseenItemCategory, attemptItemPlacement } from '../../../../api';
|
||||
import { LayoutLimitedEditionCompactPlateView, LayoutRarityLevelView, LayoutRoomPreviewerView } from '../../../../common';
|
||||
import { CatalogPostMarketplaceOfferEvent } from '../../../../events';
|
||||
import { useInventoryFurni, useInventoryUnseenTracker } from '../../../../hooks';
|
||||
import { NitroButton } from '../../../../layout';
|
||||
import { InventoryCategoryEmptyView } from '../InventoryCategoryEmptyView';
|
||||
import { InventoryFurnitureItemView } from './InventoryFurnitureItemView';
|
||||
import { InventoryFurnitureSearchView } from './InventoryFurnitureSearchView';
|
||||
|
||||
const attemptPlaceMarketplaceOffer = (groupItem: GroupItem) =>
|
||||
{
|
||||
const item = groupItem.getLastItem();
|
||||
|
||||
if(!item) return false;
|
||||
|
||||
if(!item.sellable) return false;
|
||||
|
||||
DispatchUiEvent(new CatalogPostMarketplaceOfferEvent(item));
|
||||
};
|
||||
|
||||
export const InventoryFurnitureView: FC<{
|
||||
roomSession: IRoomSession;
|
||||
roomPreviewer: RoomPreviewer;
|
||||
}> = props =>
|
||||
{
|
||||
const { roomSession = null, roomPreviewer = null } = props;
|
||||
const [ isVisible, setIsVisible ] = useState(false);
|
||||
const [ filteredGroupItems, setFilteredGroupItems ] = useState<GroupItem[]>([]);
|
||||
const { groupItems = [], selectedItem = null, activate = null, deactivate = null } = useInventoryFurni();
|
||||
const { resetItems = null } = useInventoryUnseenTracker();
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!selectedItem || !roomPreviewer) return;
|
||||
|
||||
const furnitureItem = selectedItem.getLastItem();
|
||||
|
||||
if(!furnitureItem) return;
|
||||
|
||||
const roomEngine = GetRoomEngine();
|
||||
|
||||
let wallType = roomEngine.getRoomInstanceVariable<string>(roomEngine.activeRoomId, RoomObjectVariable.ROOM_WALL_TYPE);
|
||||
let floorType = roomEngine.getRoomInstanceVariable<string>(roomEngine.activeRoomId, RoomObjectVariable.ROOM_FLOOR_TYPE);
|
||||
let landscapeType = roomEngine.getRoomInstanceVariable<string>(roomEngine.activeRoomId, RoomObjectVariable.ROOM_LANDSCAPE_TYPE);
|
||||
|
||||
wallType = (wallType && wallType.length) ? wallType : '101';
|
||||
floorType = (floorType && floorType.length) ? floorType : '101';
|
||||
landscapeType = (landscapeType && landscapeType.length) ? landscapeType : '1.1';
|
||||
|
||||
roomPreviewer.reset(false);
|
||||
roomPreviewer.updateObjectRoom(floorType, wallType, landscapeType);
|
||||
roomPreviewer.updateRoomWallsAndFloorVisibility(true, true);
|
||||
|
||||
if((furnitureItem.category === FurniCategory.WALL_PAPER) || (furnitureItem.category === FurniCategory.FLOOR) || (furnitureItem.category === FurniCategory.LANDSCAPE))
|
||||
{
|
||||
floorType = ((furnitureItem.category === FurniCategory.FLOOR) ? selectedItem.stuffData.getLegacyString() : floorType);
|
||||
wallType = ((furnitureItem.category === FurniCategory.WALL_PAPER) ? selectedItem.stuffData.getLegacyString() : wallType);
|
||||
landscapeType = ((furnitureItem.category === FurniCategory.LANDSCAPE) ? selectedItem.stuffData.getLegacyString() : landscapeType);
|
||||
|
||||
roomPreviewer.updateObjectRoom(floorType, wallType, landscapeType);
|
||||
|
||||
if(furnitureItem.category === FurniCategory.LANDSCAPE)
|
||||
{
|
||||
const data = GetSessionDataManager().getWallItemDataByName('window_double_default');
|
||||
|
||||
if(data) roomPreviewer.addWallItemIntoRoom(data.id, new Vector3d(90, 0, 0), data.customParams);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(selectedItem.isWallItem)
|
||||
{
|
||||
roomPreviewer.addWallItemIntoRoom(selectedItem.type, new Vector3d(90), furnitureItem.stuffData.getLegacyString());
|
||||
}
|
||||
else
|
||||
{
|
||||
roomPreviewer.addFurnitureIntoRoom(selectedItem.type, new Vector3d(90), selectedItem.stuffData, (furnitureItem.extra.toString()));
|
||||
}
|
||||
}
|
||||
}, [ roomPreviewer, selectedItem ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!selectedItem || !selectedItem.hasUnseenItems) return;
|
||||
|
||||
resetItems(UnseenItemCategory.FURNI, selectedItem.items.map(item => item.id));
|
||||
|
||||
selectedItem.hasUnseenItems = false;
|
||||
}, [ selectedItem, resetItems ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!isVisible) return;
|
||||
|
||||
const id = activate();
|
||||
|
||||
return () => deactivate(id);
|
||||
}, [ isVisible, activate, deactivate ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
setIsVisible(true);
|
||||
|
||||
return () => setIsVisible(false);
|
||||
}, []);
|
||||
|
||||
if(!groupItems || !groupItems.length) return <InventoryCategoryEmptyView desc={ LocalizeText('inventory.empty.desc') } title={ LocalizeText('inventory.empty.title') } />;
|
||||
|
||||
return (
|
||||
<div className="grid h-full grid-cols-12 gap-2">
|
||||
<div className="flex flex-col col-span-7 gap-1 overflow-hidden">
|
||||
<InventoryFurnitureSearchView groupItems={ groupItems } setGroupItems={ setFilteredGroupItems } />
|
||||
<InfiniteGrid<GroupItem>
|
||||
columnCount={ 6 }
|
||||
itemRender={ item => <InventoryFurnitureItemView groupItem={ item } /> }
|
||||
items={ filteredGroupItems } />
|
||||
</div>
|
||||
<div className="flex flex-col col-span-5">
|
||||
<div className="relative flex flex-col">
|
||||
<LayoutRoomPreviewerView height={ 140 } roomPreviewer={ roomPreviewer } />
|
||||
{ selectedItem && selectedItem.stuffData.isUnique &&
|
||||
<LayoutLimitedEditionCompactPlateView className="top-2 end-2" position="absolute" uniqueNumber={ selectedItem.stuffData.uniqueNumber } uniqueSeries={ selectedItem.stuffData.uniqueSeries } /> }
|
||||
{ (selectedItem && selectedItem.stuffData.rarityLevel > -1) &&
|
||||
<LayoutRarityLevelView className="top-2 end-2" level={ selectedItem.stuffData.rarityLevel } position="absolute" /> }
|
||||
</div>
|
||||
{ selectedItem &&
|
||||
<div className="flex flex-col justify-between gap-2 grow">
|
||||
<span className="text-sm truncate grow">{ selectedItem.name }</span>
|
||||
<div className="flex flex-col gap-1">
|
||||
{ !!roomSession &&
|
||||
<NitroButton onClick={ event => attemptItemPlacement(selectedItem) }>
|
||||
{ LocalizeText('inventory.furni.placetoroom') }
|
||||
</NitroButton> }
|
||||
{ (selectedItem && selectedItem.isSellable) &&
|
||||
<NitroButton onClick={ event => attemptPlaceMarketplaceOffer(selectedItem) }>
|
||||
{ LocalizeText('inventory.marketplace.sell') }
|
||||
</NitroButton> }
|
||||
</div>
|
||||
</div> }
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,279 @@
|
||||
import { IObjectData, TradingListAddItemComposer, TradingListAddItemsComposer } from '@nitrots/nitro-renderer';
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import { FaChevronLeft, FaChevronRight, FaLock, FaUnlock } from 'react-icons/fa';
|
||||
import { FurniCategory, GroupItem, IFurnitureItem, LocalizeText, NotificationAlertType, SendMessageComposer, TradeState, getGuildFurniType } from '../../../../api';
|
||||
import { AutoGrid, Button, Column, Flex, Grid, LayoutGridItem, Text } from '../../../../common';
|
||||
import { useInventoryTrade, useNotification } from '../../../../hooks';
|
||||
import { InventoryFurnitureSearchView } from './InventoryFurnitureSearchView';
|
||||
|
||||
interface InventoryTradeViewProps
|
||||
{
|
||||
cancelTrade: () => void;
|
||||
}
|
||||
|
||||
const MAX_ITEMS_TO_TRADE: number = 9;
|
||||
|
||||
export const InventoryTradeView: FC<InventoryTradeViewProps> = props =>
|
||||
{
|
||||
const { cancelTrade = null } = props;
|
||||
const [ groupItem, setGroupItem ] = useState<GroupItem>(null);
|
||||
const [ ownGroupItem, setOwnGroupItem ] = useState<GroupItem>(null);
|
||||
const [ otherGroupItem, setOtherGroupItem ] = useState<GroupItem>(null);
|
||||
const [ filteredGroupItems, setFilteredGroupItems ] = useState<GroupItem[]>(null);
|
||||
const [ countdownTick, setCountdownTick ] = useState(3);
|
||||
const [ quantity, setQuantity ] = useState<number>(1);
|
||||
const { ownUser = null, otherUser = null, groupItems = [], tradeState = TradeState.TRADING_STATE_READY, progressTrade = null, removeItem = null, setTradeState = null } = useInventoryTrade();
|
||||
const { simpleAlert = null } = useNotification();
|
||||
|
||||
const canTradeItem = (isWallItem: boolean, spriteId: number, category: number, groupable: boolean, stuffData: IObjectData) =>
|
||||
{
|
||||
if(!ownUser || ownUser.accepts || !ownUser.userItems) return false;
|
||||
|
||||
if(ownUser.userItems.length < MAX_ITEMS_TO_TRADE) return true;
|
||||
|
||||
if(!groupable) return false;
|
||||
|
||||
let type = spriteId.toString();
|
||||
|
||||
if(category === FurniCategory.POSTER)
|
||||
{
|
||||
type = ((type + 'poster') + stuffData.getLegacyString());
|
||||
}
|
||||
else
|
||||
{
|
||||
if(category === FurniCategory.GUILD_FURNI)
|
||||
{
|
||||
type = getGuildFurniType(spriteId, stuffData);
|
||||
}
|
||||
else
|
||||
{
|
||||
type = (((isWallItem) ? 'I' : 'S') + type);
|
||||
}
|
||||
}
|
||||
|
||||
return !!ownUser.userItems.getValue(type);
|
||||
};
|
||||
|
||||
const attemptItemOffer = (count: number) =>
|
||||
{
|
||||
if(!groupItem) return;
|
||||
|
||||
const tradeItems = groupItem.getTradeItems(count);
|
||||
|
||||
if(!tradeItems || !tradeItems.length) return;
|
||||
|
||||
let coreItem: IFurnitureItem = null;
|
||||
const itemIds: number[] = [];
|
||||
|
||||
for(const item of tradeItems)
|
||||
{
|
||||
itemIds.push(item.id);
|
||||
|
||||
if(!coreItem) coreItem = item;
|
||||
}
|
||||
|
||||
const ownItemCount = ownUser.userItems.length;
|
||||
|
||||
if((ownItemCount + itemIds.length) <= 1500)
|
||||
{
|
||||
if(!coreItem.isGroupable && (itemIds.length))
|
||||
{
|
||||
SendMessageComposer(new TradingListAddItemComposer(itemIds.pop()));
|
||||
}
|
||||
else
|
||||
{
|
||||
const tradeIds: number[] = [];
|
||||
|
||||
for(const itemId of itemIds)
|
||||
{
|
||||
if(canTradeItem(coreItem.isWallItem, coreItem.type, coreItem.category, coreItem.isGroupable, coreItem.stuffData))
|
||||
{
|
||||
tradeIds.push(itemId);
|
||||
}
|
||||
}
|
||||
|
||||
if(tradeIds.length)
|
||||
{
|
||||
if(tradeIds.length === 1)
|
||||
{
|
||||
SendMessageComposer(new TradingListAddItemComposer(tradeIds.pop()));
|
||||
}
|
||||
else
|
||||
{
|
||||
SendMessageComposer(new TradingListAddItemsComposer(...tradeIds));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
simpleAlert(LocalizeText('trading.items.too_many_items.desc'), NotificationAlertType.DEFAULT, null, null, LocalizeText('trading.items.too_many_items.title'));
|
||||
}
|
||||
};
|
||||
|
||||
const getLockIcon = (accepts: boolean) =>
|
||||
{
|
||||
if(accepts)
|
||||
{
|
||||
return <FaLock className="text-success fa-icon" />;
|
||||
}
|
||||
else
|
||||
{
|
||||
return <FaUnlock className="text-danger fa-icon" />;
|
||||
}
|
||||
};
|
||||
|
||||
const updateQuantity = (value: number, totalItemCount: number) =>
|
||||
{
|
||||
if(isNaN(Number(value)) || Number(value) < 0 || !value) value = 1;
|
||||
|
||||
value = Math.max(Number(value), 1);
|
||||
value = Math.min(Number(value), totalItemCount);
|
||||
|
||||
if(value === quantity) return;
|
||||
|
||||
setQuantity(value);
|
||||
};
|
||||
|
||||
const changeCount = (totalItemCount: number) =>
|
||||
{
|
||||
updateQuantity(quantity, totalItemCount);
|
||||
attemptItemOffer(quantity);
|
||||
};
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
setQuantity(1);
|
||||
}, [ groupItem ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(tradeState !== TradeState.TRADING_STATE_COUNTDOWN) return;
|
||||
|
||||
setCountdownTick(3);
|
||||
|
||||
const interval = setInterval(() =>
|
||||
{
|
||||
setCountdownTick(prevValue =>
|
||||
{
|
||||
const newValue = (prevValue - 1);
|
||||
|
||||
if(newValue === 0) clearInterval(interval);
|
||||
|
||||
return newValue;
|
||||
});
|
||||
}, 1000);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, [ tradeState, setTradeState ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(countdownTick !== 0) return;
|
||||
|
||||
setTradeState(TradeState.TRADING_STATE_CONFIRMING);
|
||||
}, [ countdownTick, setTradeState ]);
|
||||
|
||||
if((tradeState === TradeState.TRADING_STATE_READY) || !ownUser || !otherUser) return null;
|
||||
|
||||
return (
|
||||
<Grid>
|
||||
<Column overflow="hidden" size={ 4 }>
|
||||
<InventoryFurnitureSearchView groupItems={ groupItems } setGroupItems={ setFilteredGroupItems } />
|
||||
<Flex column fullHeight gap={ 2 } justifyContent="between" overflow="hidden">
|
||||
<AutoGrid columnCount={ 3 }>
|
||||
{ filteredGroupItems && (filteredGroupItems.length > 0) && filteredGroupItems.map((item, index) =>
|
||||
{
|
||||
const count = item.getUnlockedCount();
|
||||
|
||||
return (
|
||||
<LayoutGridItem key={ index } className={ !count ? 'opacity-0-5 ' : '' } itemActive={ (groupItem === item) } itemCount={ count } itemImage={ item.iconUrl } itemUniqueNumber={ item.stuffData.uniqueNumber } onClick={ event => (count && setGroupItem(item)) } onDoubleClick={ event => attemptItemOffer(1) }>
|
||||
{ ((count > 0) && (groupItem === item)) &&
|
||||
<Button className="trade-button bottom-1 end-1" position="absolute" variant="success" onClick={ event => attemptItemOffer(1) }>
|
||||
<FaChevronRight className="fa-icon" />
|
||||
</Button>
|
||||
}
|
||||
</LayoutGridItem>
|
||||
);
|
||||
}) }
|
||||
</AutoGrid>
|
||||
<Column alignItems="end" gap={ 1 }>
|
||||
<Grid overflow="hidden">
|
||||
<Column overflow="hidden" size={ 6 }>
|
||||
<input className="min-h-[calc(1.5em+ .5rem+2px)] px-[.5rem] py-[.25rem] rounded-[.2rem] form-control-sm quantity-input" disabled={ !groupItem } placeholder={ LocalizeText('catalog.bundlewidget.spinner.select.amount') } type="number" value={ quantity } onChange={ event => setQuantity(event.target.valueAsNumber) } />
|
||||
</Column>
|
||||
<Column overflow="hidden" size={ 6 }>
|
||||
<Button disabled={ !groupItem } variant="secondary" onClick={ event => changeCount(groupItem.getUnlockedCount()) }>{ LocalizeText('inventory.trading.areoffering') }</Button>
|
||||
</Column>
|
||||
</Grid>
|
||||
<div className="badge bg-muted w-full">
|
||||
{ groupItem ? groupItem.name : LocalizeText('catalog_selectproduct') }
|
||||
</div>
|
||||
</Column>
|
||||
</Flex>
|
||||
</Column>
|
||||
<Column overflow="hidden" size={ 8 }>
|
||||
<Grid overflow="hidden">
|
||||
<Column overflow="hidden" size={ 6 }>
|
||||
<div className="flex justify-between items-center">
|
||||
<Text>{ LocalizeText('inventory.trading.you') } { LocalizeText('inventory.trading.areoffering') }:</Text>
|
||||
{ getLockIcon(ownUser.accepts) }
|
||||
</div>
|
||||
<AutoGrid columnCount={ 3 }>
|
||||
{ Array.from(Array(MAX_ITEMS_TO_TRADE), (e, i) =>
|
||||
{
|
||||
const item = (ownUser.userItems.getWithIndex(i) || null);
|
||||
|
||||
if(!item) return <LayoutGridItem key={ i } />;
|
||||
|
||||
return (
|
||||
<LayoutGridItem key={ i } itemActive={ (ownGroupItem === item) } itemCount={ item.getTotalCount() } itemImage={ item.iconUrl } itemUniqueNumber={ item.stuffData.uniqueNumber } onClick={ event => setOwnGroupItem(item) } onDoubleClick={ event => removeItem(item) }>
|
||||
{ (ownGroupItem === item) &&
|
||||
<Button className="trade-button bottom-1 start-1" position="absolute" variant="danger" onClick={ event => removeItem(item) }>
|
||||
<FaChevronLeft className="fa-icon" />
|
||||
</Button> }
|
||||
</LayoutGridItem>
|
||||
);
|
||||
}) }
|
||||
</AutoGrid>
|
||||
<div className="badge bg-muted w-full">
|
||||
{ ownGroupItem ? ownGroupItem.name : LocalizeText('catalog_selectproduct') }
|
||||
</div>
|
||||
</Column>
|
||||
<Column overflow="hidden" size={ 6 }>
|
||||
<div className="flex justify-between items-center">
|
||||
<Text>{ otherUser.userName } { LocalizeText('inventory.trading.isoffering') }:</Text>
|
||||
{ getLockIcon(otherUser.accepts) }
|
||||
</div>
|
||||
<AutoGrid columnCount={ 3 }>
|
||||
{ Array.from(Array(MAX_ITEMS_TO_TRADE), (e, i) =>
|
||||
{
|
||||
const item = (otherUser.userItems.getWithIndex(i) || null);
|
||||
|
||||
if(!item) return <LayoutGridItem key={ i } />;
|
||||
|
||||
return <LayoutGridItem key={ i } itemActive={ (otherGroupItem === item) } itemCount={ item.getTotalCount() } itemImage={ item.iconUrl } itemUniqueNumber={ item.stuffData.uniqueNumber } onClick={ event => setOtherGroupItem(item) } />;
|
||||
}) }
|
||||
</AutoGrid>
|
||||
<div className="badge bg-muted w-full">
|
||||
{ otherGroupItem ? otherGroupItem.name : LocalizeText('catalog_selectproduct') }
|
||||
</div>
|
||||
</Column>
|
||||
</Grid>
|
||||
<div className="flex !flex-grow justify-between">
|
||||
<Button variant="danger" onClick={ cancelTrade }>{ LocalizeText('generic.cancel') }</Button>
|
||||
{ (tradeState === TradeState.TRADING_STATE_READY) &&
|
||||
<Button disabled={ (!ownUser.itemCount && !otherUser.itemCount) } variant="secondary" onClick={ progressTrade }>{ LocalizeText('inventory.trading.accept') }</Button> }
|
||||
{ (tradeState === TradeState.TRADING_STATE_RUNNING) &&
|
||||
<Button disabled={ (!ownUser.itemCount && !otherUser.itemCount) } variant="secondary" onClick={ progressTrade }>{ LocalizeText(ownUser.accepts ? 'inventory.trading.modify' : 'inventory.trading.accept') }</Button> }
|
||||
{ (tradeState === TradeState.TRADING_STATE_COUNTDOWN) &&
|
||||
<Button disabled variant="secondary">{ LocalizeText('inventory.trading.countdown', [ 'counter' ], [ countdownTick.toString() ]) }</Button> }
|
||||
{ (tradeState === TradeState.TRADING_STATE_CONFIRMING) &&
|
||||
<Button variant="secondary" onClick={ progressTrade }>{ LocalizeText('inventory.trading.button.restore') }</Button> }
|
||||
{ (tradeState === TradeState.TRADING_STATE_CONFIRMED) &&
|
||||
<Button variant="secondary">{ LocalizeText('inventory.trading.info.waiting') }</Button> }
|
||||
</div>
|
||||
</Column>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,44 @@
|
||||
import { MouseEventType } from '@nitrots/nitro-renderer';
|
||||
import { FC, MouseEvent, PropsWithChildren, useState } from 'react';
|
||||
import { IPetItem, UnseenItemCategory, attemptPetPlacement } from '../../../../api';
|
||||
import { LayoutPetImageView } from '../../../../common';
|
||||
import { useInventoryPets, useInventoryUnseenTracker } from '../../../../hooks';
|
||||
import { InfiniteGrid } from '../../../../layout';
|
||||
|
||||
export const InventoryPetItemView: FC<PropsWithChildren<{ petItem: IPetItem }>> = props =>
|
||||
{
|
||||
const { petItem = null, children = null, ...rest } = props;
|
||||
const [ isMouseDown, setMouseDown ] = useState(false);
|
||||
const { selectedPet = null, setSelectedPet = null } = useInventoryPets();
|
||||
const { isUnseen } = useInventoryUnseenTracker();
|
||||
const unseen = isUnseen(UnseenItemCategory.PET, petItem.petData.id);
|
||||
|
||||
const onMouseEvent = (event: MouseEvent) =>
|
||||
{
|
||||
switch(event.type)
|
||||
{
|
||||
case MouseEventType.MOUSE_DOWN:
|
||||
setSelectedPet(petItem);
|
||||
setMouseDown(true);
|
||||
return;
|
||||
case MouseEventType.MOUSE_UP:
|
||||
setMouseDown(false);
|
||||
return;
|
||||
case MouseEventType.ROLL_OUT:
|
||||
if(!isMouseDown || !(petItem === selectedPet)) return;
|
||||
|
||||
attemptPetPlacement(petItem);
|
||||
return;
|
||||
case 'dblclick':
|
||||
attemptPetPlacement(petItem);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<InfiniteGrid.Item itemActive={ (petItem === selectedPet) } itemUnseen={ unseen } onDoubleClick={ onMouseEvent } onMouseDown={ onMouseEvent } onMouseOut={ onMouseEvent } onMouseUp={ onMouseEvent } { ...rest }>
|
||||
<LayoutPetImageView direction={ 3 } figure={ petItem.petData.figureData.figuredata } headOnly={ true } />
|
||||
{ children }
|
||||
</InfiniteGrid.Item>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,89 @@
|
||||
import { GetRoomEngine, IRoomSession, RoomObjectVariable, RoomPreviewer } from '@nitrots/nitro-renderer';
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import { IPetItem, LocalizeText, UnseenItemCategory, attemptPetPlacement } from '../../../../api';
|
||||
import { LayoutRoomPreviewerView } from '../../../../common';
|
||||
import { useInventoryPets, useInventoryUnseenTracker } from '../../../../hooks';
|
||||
import { InfiniteGrid, NitroButton } from '../../../../layout';
|
||||
import { InventoryCategoryEmptyView } from '../InventoryCategoryEmptyView';
|
||||
import { InventoryPetItemView } from './InventoryPetItemView';
|
||||
|
||||
export const InventoryPetView: FC<{
|
||||
roomSession: IRoomSession;
|
||||
roomPreviewer: RoomPreviewer;
|
||||
}> = props =>
|
||||
{
|
||||
const { roomSession = null, roomPreviewer = null } = props;
|
||||
const [ isVisible, setIsVisible ] = useState(false);
|
||||
const { petItems = null, selectedPet = null, activate = null, deactivate = null } = useInventoryPets();
|
||||
const { isUnseen = null, removeUnseen = null } = useInventoryUnseenTracker();
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!selectedPet || !roomPreviewer) return;
|
||||
|
||||
const petData = selectedPet.petData;
|
||||
const roomEngine = GetRoomEngine();
|
||||
|
||||
let wallType = roomEngine.getRoomInstanceVariable<string>(roomEngine.activeRoomId, RoomObjectVariable.ROOM_WALL_TYPE);
|
||||
let floorType = roomEngine.getRoomInstanceVariable<string>(roomEngine.activeRoomId, RoomObjectVariable.ROOM_FLOOR_TYPE);
|
||||
let landscapeType = roomEngine.getRoomInstanceVariable<string>(roomEngine.activeRoomId, RoomObjectVariable.ROOM_LANDSCAPE_TYPE);
|
||||
|
||||
wallType = (wallType && wallType.length) ? wallType : '101';
|
||||
floorType = (floorType && floorType.length) ? floorType : '101';
|
||||
landscapeType = (landscapeType && landscapeType.length) ? landscapeType : '1.1';
|
||||
|
||||
roomPreviewer.reset(false);
|
||||
roomPreviewer.updateRoomWallsAndFloorVisibility(true, true);
|
||||
roomPreviewer.updateObjectRoom(floorType, wallType, landscapeType);
|
||||
roomPreviewer.addPetIntoRoom(petData.figureString);
|
||||
}, [ roomPreviewer, selectedPet ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!selectedPet || !isUnseen(UnseenItemCategory.PET, selectedPet.petData.id)) return;
|
||||
|
||||
removeUnseen(UnseenItemCategory.PET, selectedPet.petData.id);
|
||||
}, [ selectedPet, isUnseen, removeUnseen ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!isVisible) return;
|
||||
|
||||
const id = activate();
|
||||
|
||||
return () => deactivate(id);
|
||||
}, [ isVisible, activate, deactivate ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
setIsVisible(true);
|
||||
|
||||
return () => setIsVisible(false);
|
||||
}, []);
|
||||
|
||||
if(!petItems || !petItems.length) return <InventoryCategoryEmptyView desc={ LocalizeText('inventory.empty.pets.desc') } title={ LocalizeText('inventory.empty.pets.title') } />;
|
||||
|
||||
return (
|
||||
<div className="grid h-full grid-cols-12 gap-2">
|
||||
<div className="flex flex-col col-span-7 gap-1 overflow-hidden">
|
||||
<InfiniteGrid<IPetItem>
|
||||
columnCount={ 6 }
|
||||
itemRender={ item => <InventoryPetItemView petItem={ item } /> }
|
||||
items={ petItems } />
|
||||
</div>
|
||||
<div className="flex flex-col col-span-5">
|
||||
<div className="relative flex flex-col">
|
||||
<LayoutRoomPreviewerView height={ 140 } roomPreviewer={ roomPreviewer } />
|
||||
</div>
|
||||
{ selectedPet && selectedPet.petData &&
|
||||
<div className="flex flex-col justify-between gap-2 grow">
|
||||
<span className="text-sm truncate grow">{ selectedPet.petData.name }</span>
|
||||
{ !!roomSession &&
|
||||
<NitroButton onClick={ event => attemptPetPlacement(selectedPet) }>
|
||||
{ LocalizeText('inventory.furni.placetoroom') }
|
||||
</NitroButton> }
|
||||
</div> }
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user