Continue inventory updates

This commit is contained in:
Bill 2024-04-16 22:54:31 -04:00
parent 4e848fd3f5
commit 69e90bd1d3
16 changed files with 232 additions and 128 deletions

View File

@ -1,16 +1,12 @@
import { GetRenderer, GetTicker, NitroTicker, RoomPreviewer, TextureUtils } from '@nitrots/nitro-renderer'; import { GetRenderer, GetTicker, NitroTicker, RoomPreviewer, TextureUtils } from '@nitrots/nitro-renderer';
import { FC, MouseEvent, ReactNode, useEffect, useRef } from 'react'; import { FC, MouseEvent, useEffect, useRef } from 'react';
export interface LayoutRoomPreviewerViewProps export const LayoutRoomPreviewerView: FC<{
{
roomPreviewer: RoomPreviewer; roomPreviewer: RoomPreviewer;
height?: number; height?: number;
children?: ReactNode; }> = props =>
}
export const LayoutRoomPreviewerView: FC<LayoutRoomPreviewerViewProps> = props =>
{ {
const { roomPreviewer = null, height = 0, children = null } = props; const { roomPreviewer = null, height = 0 } = props;
const elementRef = useRef<HTMLDivElement>(); const elementRef = useRef<HTMLDivElement>();
const onClick = (event: MouseEvent<HTMLDivElement>) => const onClick = (event: MouseEvent<HTMLDivElement>) =>
@ -80,9 +76,14 @@ export const LayoutRoomPreviewerView: FC<LayoutRoomPreviewerViewProps> = props =
}, [ roomPreviewer, elementRef, height ]); }, [ roomPreviewer, elementRef, height ]);
return ( return (
<div className="relative w-full"> <div
<div ref={ elementRef } className="rounded-md shadow" style={ { height } } onClick={ onClick } /> ref={ elementRef }
{ children } className="relative w-full rounded-md shadow-room-previewer"
</div> style={ {
height,
minHeight: height,
maxHeight: height
} }
onClick={ onClick } />
); );
} }

View File

@ -1,10 +1,13 @@
import { GetEventDispatcher, NitroToolbarAnimateIconEvent, TextureUtils, ToolbarIconEnum } from '@nitrots/nitro-renderer'; import { GetEventDispatcher, NitroToolbarAnimateIconEvent, RoomPreviewer, TextureUtils, ToolbarIconEnum } from '@nitrots/nitro-renderer';
import { FC, useRef } from 'react'; import { FC, useRef } from 'react';
import { LayoutRoomPreviewerView, LayoutRoomPreviewerViewProps } from '../../../../common'; import { LayoutRoomPreviewerView } from '../../../../common';
import { CatalogPurchasedEvent } from '../../../../events'; import { CatalogPurchasedEvent } from '../../../../events';
import { useUiEvent } from '../../../../hooks'; import { useUiEvent } from '../../../../hooks';
export const CatalogRoomPreviewerView: FC<LayoutRoomPreviewerViewProps> = props => export const CatalogRoomPreviewerView: FC<{
roomPreviewer: RoomPreviewer;
height?: number;
}> = props =>
{ {
const { roomPreviewer = null } = props; const { roomPreviewer = null } = props;
const elementRef = useRef<HTMLDivElement>(null); const elementRef = useRef<HTMLDivElement>(null);

View File

@ -1,7 +1,8 @@
import { FC, PropsWithChildren } from 'react'; import { FC, PropsWithChildren } from 'react';
import { UnseenItemCategory } from '../../../../api'; import { UnseenItemCategory } from '../../../../api';
import { LayoutBadgeImageView, LayoutGridItem } from '../../../../common'; import { LayoutBadgeImageView } from '../../../../common';
import { useInventoryBadges, useInventoryUnseenTracker } from '../../../../hooks'; import { useInventoryBadges, useInventoryUnseenTracker } from '../../../../hooks';
import { InfiniteGrid } from '../../../../layout';
export const InventoryBadgeItemView: FC<PropsWithChildren<{ badgeCode: string }>> = props => export const InventoryBadgeItemView: FC<PropsWithChildren<{ badgeCode: string }>> = props =>
{ {
@ -11,9 +12,9 @@ export const InventoryBadgeItemView: FC<PropsWithChildren<{ badgeCode: string }>
const unseen = isUnseen(UnseenItemCategory.BADGE, getBadgeId(badgeCode)); const unseen = isUnseen(UnseenItemCategory.BADGE, getBadgeId(badgeCode));
return ( return (
<LayoutGridItem itemActive={ (selectedBadgeCode === badgeCode) } itemUnseen={ unseen } onDoubleClick={ event => toggleBadge(selectedBadgeCode) } onMouseDown={ event => setSelectedBadgeCode(badgeCode) } { ...rest }> <InfiniteGrid.Item itemActive={ (selectedBadgeCode === badgeCode) } itemUnseen={ unseen } onDoubleClick={ event => toggleBadge(selectedBadgeCode) } onMouseDown={ event => setSelectedBadgeCode(badgeCode) } { ...rest }>
<LayoutBadgeImageView badgeCode={ badgeCode } /> <LayoutBadgeImageView badgeCode={ badgeCode } />
{ children } { children }
</LayoutGridItem> </InfiniteGrid.Item>
); );
} }

View File

@ -1,7 +1,8 @@
import { FC, useEffect, useState } from 'react'; import { FC, useEffect, useState } from 'react';
import { LocalizeBadgeName, LocalizeText, UnseenItemCategory } from '../../../../api'; import { LocalizeBadgeName, LocalizeText, UnseenItemCategory } from '../../../../api';
import { AutoGrid, Button, Column, Grid, LayoutBadgeImageView, Text } from '../../../../common'; import { LayoutBadgeImageView } from '../../../../common';
import { useInventoryBadges, useInventoryUnseenTracker } from '../../../../hooks'; import { useInventoryBadges, useInventoryUnseenTracker } from '../../../../hooks';
import { InfiniteGrid, NitroButton } from '../../../../layout';
import { InventoryBadgeItemView } from './InventoryBadgeItemView'; import { InventoryBadgeItemView } from './InventoryBadgeItemView';
export const InventoryBadgeView: FC<{}> = props => export const InventoryBadgeView: FC<{}> = props =>
@ -34,33 +35,36 @@ export const InventoryBadgeView: FC<{}> = props =>
}, []); }, []);
return ( return (
<Grid> <div className="grid h-full grid-cols-12 gap-2">
<Column overflow="hidden" size={ 7 }> <div className="flex flex-col col-span-7 gap-1 overflow-hidden">
<AutoGrid columnCount={ 4 }> <InfiniteGrid<string>
{ badgeCodes && (badgeCodes.length > 0) && badgeCodes.map((badgeCode, index) => columnCount={ 5 }
{ estimateSize={ 50 }
if(isWearingBadge(badgeCode)) return null; itemRender={ item => <InventoryBadgeItemView badgeCode={ item } /> }
items={ badgeCodes.filter(code => !isWearingBadge(code)) } />
return <InventoryBadgeItemView key={ index } badgeCode={ badgeCode } /> </div>
}) } <div className="flex flex-col justify-between col-span-5 overflow-auto">
</AutoGrid> <div className="flex flex-col gap-2 overflow-hidden">
</Column> <span className="text-sm truncate grow">{ LocalizeText('inventory.badges.activebadges') }</span>
<Column className="justify-content-between" overflow="auto" size={ 5 }> <InfiniteGrid<string>
<Column gap={ 2 } overflow="hidden"> columnCount={ 3 }
<Text>{ LocalizeText('inventory.badges.activebadges') }</Text> estimateSize={ 50 }
<AutoGrid columnCount={ 3 }> itemRender={ item => <InventoryBadgeItemView badgeCode={ item } /> }
{ activeBadgeCodes && (activeBadgeCodes.length > 0) && activeBadgeCodes.map((badgeCode, index) => <InventoryBadgeItemView key={ index } badgeCode={ badgeCode } />) } items={ activeBadgeCodes } />
</AutoGrid> </div>
</Column>
{ !!selectedBadgeCode && { !!selectedBadgeCode &&
<Column grow gap={ 2 } justifyContent="end"> <div className="flex flex-col gap-2">
<div className="items-center gap-2"> <div className="items-center gap-2">
<LayoutBadgeImageView shrink badgeCode={ selectedBadgeCode } /> <LayoutBadgeImageView shrink badgeCode={ selectedBadgeCode } />
<Text>{ LocalizeBadgeName(selectedBadgeCode) }</Text> <span className="text-sm truncate grow">{ LocalizeBadgeName(selectedBadgeCode) }</span>
</div> </div>
<Button disabled={ !isWearingBadge(selectedBadgeCode) && !canWearBadges() } variant={ (isWearingBadge(selectedBadgeCode) ? 'danger' : 'success') } onClick={ event => toggleBadge(selectedBadgeCode) }>{ LocalizeText(isWearingBadge(selectedBadgeCode) ? 'inventory.badges.clearbadge' : 'inventory.badges.wearbadge') }</Button> <NitroButton
</Column> } disabled={ !isWearingBadge(selectedBadgeCode) && !canWearBadges() }
</Column> onClick={ event => toggleBadge(selectedBadgeCode) }>
</Grid> { LocalizeText(isWearingBadge(selectedBadgeCode) ? 'inventory.badges.clearbadge' : 'inventory.badges.wearbadge') }
</NitroButton>
</div> }
</div>
</div>
); );
} }

View File

@ -1,10 +1,13 @@
import { MouseEventType } from '@nitrots/nitro-renderer'; import { MouseEventType } from '@nitrots/nitro-renderer';
import { FC, MouseEvent, PropsWithChildren, useState } from 'react'; import { FC, MouseEvent, PropsWithChildren, useState } from 'react';
import { attemptBotPlacement, IBotItem, UnseenItemCategory } from '../../../../api'; import { IBotItem, UnseenItemCategory, attemptBotPlacement } from '../../../../api';
import { LayoutAvatarImageView, LayoutGridItem } from '../../../../common'; import { LayoutAvatarImageView } from '../../../../common';
import { useInventoryBots, useInventoryUnseenTracker } from '../../../../hooks'; import { useInventoryBots, useInventoryUnseenTracker } from '../../../../hooks';
import { InfiniteGrid } from '../../../../layout';
export const InventoryBotItemView: FC<PropsWithChildren<{ botItem: IBotItem }>> = props => export const InventoryBotItemView: FC<PropsWithChildren<{
botItem: IBotItem
}>> = props =>
{ {
const { botItem = null, children = null, ...rest } = props; const { botItem = null, children = null, ...rest } = props;
const [ isMouseDown, setMouseDown ] = useState(false); const [ isMouseDown, setMouseDown ] = useState(false);
@ -35,9 +38,9 @@ export const InventoryBotItemView: FC<PropsWithChildren<{ botItem: IBotItem }>>
} }
return ( return (
<LayoutGridItem itemActive={ (selectedBot === botItem) } itemUnseen={ unseen } onDoubleClick={ onMouseEvent } onMouseDown={ onMouseEvent } onMouseOut={ onMouseEvent } onMouseUp={ onMouseEvent } { ...rest }> <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 } /> <LayoutAvatarImageView direction={ 3 } figure={ botItem.botData.figure } headOnly={ true } />
{ children } { children }
</LayoutGridItem> </InfiniteGrid.Item>
); );
} }

View File

@ -1,18 +1,16 @@
import { GetRoomEngine, IRoomSession, RoomObjectVariable, RoomPreviewer } from '@nitrots/nitro-renderer'; import { GetRoomEngine, IRoomSession, RoomObjectVariable, RoomPreviewer } from '@nitrots/nitro-renderer';
import { FC, useEffect, useState } from 'react'; import { FC, useEffect, useState } from 'react';
import { LocalizeText, UnseenItemCategory, attemptBotPlacement } from '../../../../api'; import { IBotItem, LocalizeText, UnseenItemCategory, attemptBotPlacement } from '../../../../api';
import { AutoGrid, Button, Column, Grid, LayoutRoomPreviewerView, Text } from '../../../../common'; import { LayoutRoomPreviewerView } from '../../../../common';
import { useInventoryBots, useInventoryUnseenTracker } from '../../../../hooks'; import { useInventoryBots, useInventoryUnseenTracker } from '../../../../hooks';
import { InfiniteGrid, NitroButton } from '../../../../layout';
import { InventoryCategoryEmptyView } from '../InventoryCategoryEmptyView'; import { InventoryCategoryEmptyView } from '../InventoryCategoryEmptyView';
import { InventoryBotItemView } from './InventoryBotItemView'; import { InventoryBotItemView } from './InventoryBotItemView';
interface InventoryBotViewProps export const InventoryBotView: FC<{
{
roomSession: IRoomSession; roomSession: IRoomSession;
roomPreviewer: RoomPreviewer; roomPreviewer: RoomPreviewer;
} }> = props =>
export const InventoryBotView: FC<InventoryBotViewProps> = props =>
{ {
const { roomSession = null, roomPreviewer = null } = props; const { roomSession = null, roomPreviewer = null } = props;
const [ isVisible, setIsVisible ] = useState(false); const [ isVisible, setIsVisible ] = useState(false);
@ -67,25 +65,26 @@ export const InventoryBotView: FC<InventoryBotViewProps> = props =>
if(!botItems || !botItems.length) return <InventoryCategoryEmptyView desc={ LocalizeText('inventory.empty.bots.desc') } title={ LocalizeText('inventory.empty.bots.title') } />; if(!botItems || !botItems.length) return <InventoryCategoryEmptyView desc={ LocalizeText('inventory.empty.bots.desc') } title={ LocalizeText('inventory.empty.bots.title') } />;
return ( return (
<Grid> <div className="grid h-full grid-cols-12 gap-2">
<Column overflow="hidden" size={ 7 }> <div className="flex flex-col col-span-7 gap-1 overflow-hidden">
<AutoGrid columnCount={ 5 }> <InfiniteGrid<IBotItem>
{ botItems && (botItems.length > 0) && botItems.map(item => <InventoryBotItemView key={ item.botData.id } botItem={ item } />) } columnCount={ 6 }
</AutoGrid> itemRender={ item => <InventoryBotItemView botItem={ item } /> }
</Column> items={ botItems } />
<Column overflow="auto" size={ 5 }> </div>
<Column overflow="hidden" position="relative"> <div className="flex flex-col col-span-5">
<div className="relative flex flex-col">
<LayoutRoomPreviewerView height={ 140 } roomPreviewer={ roomPreviewer } /> <LayoutRoomPreviewerView height={ 140 } roomPreviewer={ roomPreviewer } />
</Column> </div>
{ selectedBot && { selectedBot &&
<Column grow gap={ 2 } justifyContent="between"> <div className="flex flex-col justify-between gap-2 grow">
<Text grow truncate>{ selectedBot.botData.name }</Text> <span className="truncate grow">{ selectedBot.botData.name }</span>
{ !!roomSession && { !!roomSession &&
<Button variant="success" onClick={ event => attemptBotPlacement(selectedBot) }> <NitroButton onClick={ event => attemptBotPlacement(selectedBot) }>
{ LocalizeText('inventory.furni.placetoroom') } { LocalizeText('inventory.furni.placetoroom') }
</Button> } </NitroButton> }
</Column> } </div> }
</Column> </div>
</Grid> </div>
); );
} }

View File

@ -4,7 +4,9 @@ import { GroupItem, attemptItemPlacement } from '../../../../api';
import { useInventoryFurni } from '../../../../hooks'; import { useInventoryFurni } from '../../../../hooks';
import { InfiniteGrid, classNames } from '../../../../layout'; import { InfiniteGrid, classNames } from '../../../../layout';
export const InventoryFurnitureItemView: FC<{ groupItem: GroupItem }> = props => export const InventoryFurnitureItemView: FC<{
groupItem: GroupItem
}> = props =>
{ {
const { groupItem = null, ...rest } = props; const { groupItem = null, ...rest } = props;
const [ isMouseDown, setMouseDown ] = useState(false); const [ isMouseDown, setMouseDown ] = useState(false);

View File

@ -1,15 +1,12 @@
import { Dispatch, FC, SetStateAction, useEffect, useState } from 'react'; import { Dispatch, FC, SetStateAction, useEffect, useState } from 'react';
import { FaSearch } from 'react-icons/fa'; import { FaSearch } from 'react-icons/fa';
import { GroupItem, LocalizeText } from '../../../../api'; import { GroupItem, LocalizeText } from '../../../../api';
import { Button } from '../../../../common'; import { NitroButton, NitroInput } from '../../../../layout';
export interface InventoryFurnitureSearchViewProps export const InventoryFurnitureSearchView: FC<{
{
groupItems: GroupItem[]; groupItems: GroupItem[];
setGroupItems: Dispatch<SetStateAction<GroupItem[]>>; setGroupItems: Dispatch<SetStateAction<GroupItem[]>>;
} }> = props =>
export const InventoryFurnitureSearchView: FC<InventoryFurnitureSearchViewProps> = props =>
{ {
const { groupItems = [], setGroupItems = null } = props; const { groupItems = [], setGroupItems = null } = props;
const [ searchValue, setSearchValue ] = useState(''); const [ searchValue, setSearchValue ] = useState('');
@ -38,10 +35,13 @@ export const InventoryFurnitureSearchView: FC<InventoryFurnitureSearchViewProps>
return ( return (
<div className="flex gap-1"> <div className="flex gap-1">
<input className="form-control form-control-sm" placeholder={ LocalizeText('generic.search') } type="text" value={ searchValue } onChange={ event => setSearchValue(event.target.value) } /> <NitroInput
<Button variant="primary"> placeholder={ LocalizeText('generic.search') }
value={ searchValue }
onChange={ event => setSearchValue(event.target.value) } />
<NitroButton>
<FaSearch className="fa-icon" /> <FaSearch className="fa-icon" />
</Button> </NitroButton>
</div> </div>
); );
} }

View File

@ -2,19 +2,14 @@ import { InfiniteGrid } from '@layout/InfiniteGrid';
import { GetRoomEngine, GetSessionDataManager, IRoomSession, RoomObjectVariable, RoomPreviewer, Vector3d } from '@nitrots/nitro-renderer'; import { GetRoomEngine, GetSessionDataManager, IRoomSession, RoomObjectVariable, RoomPreviewer, Vector3d } from '@nitrots/nitro-renderer';
import { FC, useEffect, useState } from 'react'; import { FC, useEffect, useState } from 'react';
import { DispatchUiEvent, FurniCategory, GroupItem, LocalizeText, UnseenItemCategory, attemptItemPlacement } from '../../../../api'; import { DispatchUiEvent, FurniCategory, GroupItem, LocalizeText, UnseenItemCategory, attemptItemPlacement } from '../../../../api';
import { Button, LayoutLimitedEditionCompactPlateView, LayoutRarityLevelView, LayoutRoomPreviewerView } from '../../../../common'; import { LayoutLimitedEditionCompactPlateView, LayoutRarityLevelView, LayoutRoomPreviewerView } from '../../../../common';
import { CatalogPostMarketplaceOfferEvent } from '../../../../events'; import { CatalogPostMarketplaceOfferEvent } from '../../../../events';
import { useInventoryFurni, useInventoryUnseenTracker } from '../../../../hooks'; import { useInventoryFurni, useInventoryUnseenTracker } from '../../../../hooks';
import { NitroButton } from '../../../../layout';
import { InventoryCategoryEmptyView } from '../InventoryCategoryEmptyView'; import { InventoryCategoryEmptyView } from '../InventoryCategoryEmptyView';
import { InventoryFurnitureItemView } from './InventoryFurnitureItemView'; import { InventoryFurnitureItemView } from './InventoryFurnitureItemView';
import { InventoryFurnitureSearchView } from './InventoryFurnitureSearchView'; import { InventoryFurnitureSearchView } from './InventoryFurnitureSearchView';
interface InventoryFurnitureViewProps
{
roomSession: IRoomSession;
roomPreviewer: RoomPreviewer;
}
const attemptPlaceMarketplaceOffer = (groupItem: GroupItem) => const attemptPlaceMarketplaceOffer = (groupItem: GroupItem) =>
{ {
const item = groupItem.getLastItem(); const item = groupItem.getLastItem();
@ -26,7 +21,10 @@ const attemptPlaceMarketplaceOffer = (groupItem: GroupItem) =>
DispatchUiEvent(new CatalogPostMarketplaceOfferEvent(item)); DispatchUiEvent(new CatalogPostMarketplaceOfferEvent(item));
} }
export const InventoryFurnitureView: FC<InventoryFurnitureViewProps> = props => export const InventoryFurnitureView: FC<{
roomSession: IRoomSession;
roomPreviewer: RoomPreviewer;
}> = props =>
{ {
const { roomSession = null, roomPreviewer = null } = props; const { roomSession = null, roomPreviewer = null } = props;
const [ isVisible, setIsVisible ] = useState(false); const [ isVisible, setIsVisible ] = useState(false);
@ -112,7 +110,7 @@ export const InventoryFurnitureView: FC<InventoryFurnitureViewProps> = props =>
if(!groupItems || !groupItems.length) return <InventoryCategoryEmptyView desc={ LocalizeText('inventory.empty.desc') } title={ LocalizeText('inventory.empty.title') } />; if(!groupItems || !groupItems.length) return <InventoryCategoryEmptyView desc={ LocalizeText('inventory.empty.desc') } title={ LocalizeText('inventory.empty.title') } />;
return ( return (
<div className="grid h-full grid-cols-12 gap-2 overflow-hidden"> <div className="grid h-full grid-cols-12 gap-2">
<div className="flex flex-col col-span-7 gap-1 overflow-hidden"> <div className="flex flex-col col-span-7 gap-1 overflow-hidden">
<InventoryFurnitureSearchView groupItems={ groupItems } setGroupItems={ setFilteredGroupItems } /> <InventoryFurnitureSearchView groupItems={ groupItems } setGroupItems={ setFilteredGroupItems } />
<InfiniteGrid<GroupItem> <InfiniteGrid<GroupItem>
@ -120,8 +118,8 @@ export const InventoryFurnitureView: FC<InventoryFurnitureViewProps> = props =>
itemRender={ item => <InventoryFurnitureItemView groupItem={ item } /> } itemRender={ item => <InventoryFurnitureItemView groupItem={ item } /> }
items={ filteredGroupItems } /> items={ filteredGroupItems } />
</div> </div>
<div className="flex flex-col col-span-5 overflow-hidden"> <div className="flex flex-col col-span-5">
<div className="relative flex flex-col overflow-hidden"> <div className="relative flex flex-col">
<LayoutRoomPreviewerView height={ 140 } roomPreviewer={ roomPreviewer } /> <LayoutRoomPreviewerView height={ 140 } roomPreviewer={ roomPreviewer } />
{ selectedItem && selectedItem.stuffData.isUnique && { selectedItem && selectedItem.stuffData.isUnique &&
<LayoutLimitedEditionCompactPlateView className="top-2 end-2" position="absolute" uniqueNumber={ selectedItem.stuffData.uniqueNumber } uniqueSeries={ selectedItem.stuffData.uniqueSeries } /> } <LayoutLimitedEditionCompactPlateView className="top-2 end-2" position="absolute" uniqueNumber={ selectedItem.stuffData.uniqueNumber } uniqueSeries={ selectedItem.stuffData.uniqueSeries } /> }
@ -130,16 +128,16 @@ export const InventoryFurnitureView: FC<InventoryFurnitureViewProps> = props =>
</div> </div>
{ selectedItem && { selectedItem &&
<div className="flex flex-col justify-between gap-2 grow"> <div className="flex flex-col justify-between gap-2 grow">
<span className="truncate grow">{ selectedItem.name }</span> <span className="text-sm truncate grow">{ selectedItem.name }</span>
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-1">
{ !!roomSession && { !!roomSession &&
<Button variant="success" onClick={ event => attemptItemPlacement(selectedItem) }> <NitroButton onClick={ event => attemptItemPlacement(selectedItem) }>
{ LocalizeText('inventory.furni.placetoroom') } { LocalizeText('inventory.furni.placetoroom') }
</Button> } </NitroButton> }
{ (selectedItem && selectedItem.isSellable) && { (selectedItem && selectedItem.isSellable) &&
<Button onClick={ event => attemptPlaceMarketplaceOffer(selectedItem) }> <NitroButton onClick={ event => attemptPlaceMarketplaceOffer(selectedItem) }>
{ LocalizeText('inventory.marketplace.sell') } { LocalizeText('inventory.marketplace.sell') }
</Button> } </NitroButton> }
</div> </div>
</div> } </div> }
</div> </div>

View File

@ -1,8 +1,9 @@
import { MouseEventType } from '@nitrots/nitro-renderer'; import { MouseEventType } from '@nitrots/nitro-renderer';
import { FC, MouseEvent, PropsWithChildren, useState } from 'react'; import { FC, MouseEvent, PropsWithChildren, useState } from 'react';
import { attemptPetPlacement, IPetItem, UnseenItemCategory } from '../../../../api'; import { IPetItem, UnseenItemCategory, attemptPetPlacement } from '../../../../api';
import { LayoutGridItem, LayoutPetImageView } from '../../../../common'; import { LayoutPetImageView } from '../../../../common';
import { useInventoryPets, useInventoryUnseenTracker } from '../../../../hooks'; import { useInventoryPets, useInventoryUnseenTracker } from '../../../../hooks';
import { InfiniteGrid } from '../../../../layout';
export const InventoryPetItemView: FC<PropsWithChildren<{ petItem: IPetItem }>> = props => export const InventoryPetItemView: FC<PropsWithChildren<{ petItem: IPetItem }>> = props =>
{ {
@ -35,9 +36,9 @@ export const InventoryPetItemView: FC<PropsWithChildren<{ petItem: IPetItem }>>
} }
return ( return (
<LayoutGridItem itemActive={ (petItem === selectedPet) } itemUnseen={ unseen } onDoubleClick={ onMouseEvent } onMouseDown={ onMouseEvent } onMouseOut={ onMouseEvent } onMouseUp={ onMouseEvent } { ...rest }> <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 } /> <LayoutPetImageView direction={ 3 } figure={ petItem.petData.figureData.figuredata } headOnly={ true } />
{ children } { children }
</LayoutGridItem> </InfiniteGrid.Item>
); );
} }

View File

@ -1,18 +1,16 @@
import { GetRoomEngine, IRoomSession, RoomObjectVariable, RoomPreviewer } from '@nitrots/nitro-renderer'; import { GetRoomEngine, IRoomSession, RoomObjectVariable, RoomPreviewer } from '@nitrots/nitro-renderer';
import { FC, useEffect, useState } from 'react'; import { FC, useEffect, useState } from 'react';
import { LocalizeText, UnseenItemCategory, attemptPetPlacement } from '../../../../api'; import { IPetItem, LocalizeText, UnseenItemCategory, attemptPetPlacement } from '../../../../api';
import { AutoGrid, Button, Column, Grid, LayoutRoomPreviewerView, Text } from '../../../../common'; import { LayoutRoomPreviewerView } from '../../../../common';
import { useInventoryPets, useInventoryUnseenTracker } from '../../../../hooks'; import { useInventoryPets, useInventoryUnseenTracker } from '../../../../hooks';
import { InfiniteGrid, NitroButton } from '../../../../layout';
import { InventoryCategoryEmptyView } from '../InventoryCategoryEmptyView'; import { InventoryCategoryEmptyView } from '../InventoryCategoryEmptyView';
import { InventoryPetItemView } from './InventoryPetItemView'; import { InventoryPetItemView } from './InventoryPetItemView';
interface InventoryPetViewProps export const InventoryPetView: FC<{
{
roomSession: IRoomSession; roomSession: IRoomSession;
roomPreviewer: RoomPreviewer; roomPreviewer: RoomPreviewer;
} }> = props =>
export const InventoryPetView: FC<InventoryPetViewProps> = props =>
{ {
const { roomSession = null, roomPreviewer = null } = props; const { roomSession = null, roomPreviewer = null } = props;
const [ isVisible, setIsVisible ] = useState(false); const [ isVisible, setIsVisible ] = useState(false);
@ -66,25 +64,26 @@ export const InventoryPetView: FC<InventoryPetViewProps> = props =>
if(!petItems || !petItems.length) return <InventoryCategoryEmptyView desc={ LocalizeText('inventory.empty.pets.desc') } title={ LocalizeText('inventory.empty.pets.title') } />; if(!petItems || !petItems.length) return <InventoryCategoryEmptyView desc={ LocalizeText('inventory.empty.pets.desc') } title={ LocalizeText('inventory.empty.pets.title') } />;
return ( return (
<Grid> <div className="grid h-full grid-cols-12 gap-2">
<Column overflow="hidden" size={ 7 }> <div className="flex flex-col col-span-7 gap-1 overflow-hidden">
<AutoGrid columnCount={ 5 }> <InfiniteGrid<IPetItem>
{ petItems && (petItems.length > 0) && petItems.map(item => <InventoryPetItemView key={ item.petData.id } petItem={ item } />) } columnCount={ 6 }
</AutoGrid> itemRender={ item => <InventoryPetItemView petItem={ item } /> }
</Column> items={ petItems } />
<Column overflow="auto" size={ 5 }> </div>
<Column overflow="hidden" position="relative"> <div className="flex flex-col col-span-5">
<div className="relative flex flex-col">
<LayoutRoomPreviewerView height={ 140 } roomPreviewer={ roomPreviewer } /> <LayoutRoomPreviewerView height={ 140 } roomPreviewer={ roomPreviewer } />
</Column> </div>
{ selectedPet && selectedPet.petData && { selectedPet && selectedPet.petData &&
<Column grow gap={ 2 } justifyContent="between"> <div className="flex flex-col justify-between gap-2 grow">
<Text grow truncate>{ selectedPet.petData.name }</Text> <span className="text-sm truncate grow">{ selectedPet.petData.name }</span>
{ !!roomSession && { !!roomSession &&
<Button variant="success" onClick={ event => attemptPetPlacement(selectedPet) }> <NitroButton onClick={ event => attemptPetPlacement(selectedPet) }>
{ LocalizeText('inventory.furni.placetoroom') } { LocalizeText('inventory.furni.placetoroom') }
</Button> } </NitroButton> }
</Column> } </div> }
</Column> </div>
</Grid> </div>
); );
} }

View File

@ -69,9 +69,10 @@ const InfiniteGridRoot = <T,>(props: Props<T>) =>
<div <div
key={ virtualRow.key + 'a' } key={ virtualRow.key + 'a' }
ref={ virtualizer.measureElement } ref={ virtualizer.measureElement }
className={ `grid grid-cols-${ columnCount } gap-1 absolute top-0 left-0 h-[45px] last:pb-0 w-full` } className={ `grid grid-cols-${ columnCount } gap-1 absolute top-0 left-0 last:pb-0 w-full` }
data-index={ virtualRow.index } data-index={ virtualRow.index }
style={ { style={ {
height: virtualRow.size,
transform: `translateY(${ virtualRow.start }px)` transform: `translateY(${ virtualRow.start }px)`
} }> } }>
{ Array.from(Array(columnCount)).map((e,i) => { Array.from(Array(columnCount)).map((e,i) =>
@ -142,7 +143,7 @@ const InfiniteGridItem = forwardRef<HTMLDivElement, {
ref={ ref } ref={ ref }
className={ classNames( className={ classNames(
'flex flex-col items-center justify-center cursor-pointer overflow-hidden relative bg-center bg-no-repeat w-full rounded-md border-2', 'flex flex-col items-center justify-center cursor-pointer overflow-hidden relative bg-center bg-no-repeat w-full rounded-md border-2',
(!backgroundImageUrl || !backgroundImageUrl.length) && 'nitro-icon icon-loading', (itemImage && (!backgroundImageUrl || !backgroundImageUrl.length)) && 'nitro-icon icon-loading',
itemActive ? 'border-card-grid-item-active bg-card-grid-item-active' : 'border-card-grid-item-border bg-card-grid-item', itemActive ? 'border-card-grid-item-active bg-card-grid-item-active' : 'border-card-grid-item-border bg-card-grid-item',
(itemUniqueSoldout || (itemUniqueNumber > 0)) && 'unique-item', (itemUniqueSoldout || (itemUniqueNumber > 0)) && 'unique-item',
itemUniqueSoldout && 'sold-out', itemUniqueSoldout && 'sold-out',

View File

@ -0,0 +1,44 @@
import { ButtonHTMLAttributes, DetailedHTMLProps, forwardRef, PropsWithChildren } from 'react';
import { classNames } from './classNames';
const classes = {
base: 'inline-flex justify-center items-center gap-2 transition-[background-color] duration-300 transform tracking-wide rounded-md',
disabled: '',
size: {
default: 'px-2 py-0.5 text-sm font-medium',
lg: 'px-5 py-3 text-base font-medium',
xl: 'px-6 py-3.5 text-base font-medium',
},
outline: {
default: 'text-blue-700 hover:text-white border border-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 dark:border-blue-500 dark:text-blue-500 dark:hover:text-white dark:hover:bg-blue-600 dark:focus:ring-blue-800'
},
color: {
default: 'bg-button-gradient-gray border border-gray-500',
}
}
export const NitroButton = forwardRef<HTMLButtonElement, PropsWithChildren<{
color?: 'default' | 'dark' | 'ghost';
size?: 'default' | 'lg' | 'xl';
outline?: boolean;
}> & DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>>((props, ref) =>
{
const { color = 'default', size = 'default', outline = false, disabled = false, type = 'button', className = null, ...rest } = props;
return (
<button
ref={ ref }
className={ classNames(
classes.base,
classes.size[size],
outline ? classes.outline[color] : classes.color[color],
disabled && classes.disabled,
className
) }
disabled={ disabled }
type={ type }
{ ...rest } />
);
});
NitroButton.displayName = 'NitroButton';

42
src/layout/NitroInput.tsx Normal file
View File

@ -0,0 +1,42 @@
import { DetailedHTMLProps, forwardRef, InputHTMLAttributes, PropsWithChildren } from 'react';
import { classNames } from './classNames';
const classes = {
base: 'block w-full placeholder-gray-400 border border-gray-300 shadow-sm appearance-none',
disabled: '',
size: {
default: 'px-2 py-2 text-sm font-medium',
},
rounded: 'rounded-md',
color: {
default: 'focus:outline-none focus:ring-indigo-500 focus:border-indigo-500',
}
}
export const NitroInput = forwardRef<HTMLInputElement, PropsWithChildren<{
color?: 'default' | 'dark' | 'ghost';
inputSize?: 'xs' | 'sm' | 'default' | 'lg' | 'xl';
rounded?: boolean;
}> & DetailedHTMLProps<InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>>((props, ref) =>
{
const { color = 'default', inputSize = 'default', rounded = true, disabled = false, type = 'text', autoComplete = 'off', className = null, ...rest } = props;
return (
<input
ref={ ref }
autoComplete={ autoComplete }
className={ classNames(
classes.base,
classes.size[inputSize],
rounded && classes.rounded,
classes.color[color],
disabled && classes.disabled,
className
) }
disabled={ disabled }
type={ type }
{ ...rest } />
);
});
NitroInput.displayName = 'NitroInput';

View File

@ -1,5 +1,7 @@
export * from './InfiniteGrid'; export * from './InfiniteGrid';
export * from './NitroButton';
export * from './NitroCard'; export * from './NitroCard';
export * from './NitroInput';
export * from './NitroItemCountBadge'; export * from './NitroItemCountBadge';
export * from './classNames'; export * from './classNames';
export * from './limited-edition'; export * from './limited-edition';

View File

@ -19,7 +19,8 @@ const colors = {
}; };
const boxShadow = { const boxShadow = {
'inner1px': 'inset 0 0 0 1px rgba(255,255,255,.3)' 'inner1px': 'inset 0 0 0 1px rgba(255,255,255,.3)',
'room-previewer': '-2px -2px rgba(0, 0, 0, 0.4), inset 3px 3px rgba(0, 0, 0, 0.2);'
}; };
@ -31,6 +32,9 @@ module.exports = {
}, },
colors: generateShades(colors), colors: generateShades(colors),
boxShadow, boxShadow,
backgroundImage: {
'button-gradient-gray': 'linear-gradient(to bottom, #e2e2e2 50%, #c8c8c8 50%)',
},
spacing: { spacing: {
'card-header': '33px', 'card-header': '33px',
'card-tabs': '33px', 'card-tabs': '33px',