Compare commits

...

3 Commits

Author SHA1 Message Date
Bill 69e90bd1d3 Continue inventory updates 2024-04-16 22:54:31 -04:00
Bill 4e848fd3f5 Add ltd stuff 2024-04-16 17:59:40 -04:00
robbis95 d416c82741 Added loading screen 2024-04-16 22:42:37 +02:00
21 changed files with 570 additions and 138 deletions

View File

@ -1,16 +1,12 @@
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;
height?: number;
children?: ReactNode;
}
export const LayoutRoomPreviewerView: FC<LayoutRoomPreviewerViewProps> = props =>
}> = props =>
{
const { roomPreviewer = null, height = 0, children = null } = props;
const { roomPreviewer = null, height = 0 } = props;
const elementRef = useRef<HTMLDivElement>();
const onClick = (event: MouseEvent<HTMLDivElement>) =>
@ -80,9 +76,14 @@ export const LayoutRoomPreviewerView: FC<LayoutRoomPreviewerViewProps> = props =
}, [ roomPreviewer, elementRef, height ]);
return (
<div className="relative w-full">
<div ref={ elementRef } className="rounded-md shadow" style={ { height } } onClick={ onClick } />
{ children }
</div>
<div
ref={ elementRef }
className="relative w-full rounded-md shadow-room-previewer"
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 { LayoutRoomPreviewerView, LayoutRoomPreviewerViewProps } from '../../../../common';
import { LayoutRoomPreviewerView } from '../../../../common';
import { CatalogPurchasedEvent } from '../../../../events';
import { useUiEvent } from '../../../../hooks';
export const CatalogRoomPreviewerView: FC<LayoutRoomPreviewerViewProps> = props =>
export const CatalogRoomPreviewerView: FC<{
roomPreviewer: RoomPreviewer;
height?: number;
}> = props =>
{
const { roomPreviewer = null } = props;
const elementRef = useRef<HTMLDivElement>(null);

View File

@ -1,7 +1,8 @@
import { FC, PropsWithChildren } from 'react';
import { UnseenItemCategory } from '../../../../api';
import { LayoutBadgeImageView, LayoutGridItem } from '../../../../common';
import { LayoutBadgeImageView } from '../../../../common';
import { useInventoryBadges, useInventoryUnseenTracker } from '../../../../hooks';
import { InfiniteGrid } from '../../../../layout';
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));
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 } />
{ children }
</LayoutGridItem>
</InfiniteGrid.Item>
);
}
}

View File

@ -1,7 +1,8 @@
import { FC, useEffect, useState } from 'react';
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 { InfiniteGrid, NitroButton } from '../../../../layout';
import { InventoryBadgeItemView } from './InventoryBadgeItemView';
export const InventoryBadgeView: FC<{}> = props =>
@ -34,33 +35,36 @@ export const InventoryBadgeView: FC<{}> = props =>
}, []);
return (
<Grid>
<Column overflow="hidden" size={ 7 }>
<AutoGrid columnCount={ 4 }>
{ badgeCodes && (badgeCodes.length > 0) && badgeCodes.map((badgeCode, index) =>
{
if(isWearingBadge(badgeCode)) return null;
return <InventoryBadgeItemView key={ index } badgeCode={ badgeCode } />
}) }
</AutoGrid>
</Column>
<Column className="justify-content-between" overflow="auto" size={ 5 }>
<Column gap={ 2 } overflow="hidden">
<Text>{ LocalizeText('inventory.badges.activebadges') }</Text>
<AutoGrid columnCount={ 3 }>
{ activeBadgeCodes && (activeBadgeCodes.length > 0) && activeBadgeCodes.map((badgeCode, index) => <InventoryBadgeItemView key={ index } badgeCode={ badgeCode } />) }
</AutoGrid>
</Column>
<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 &&
<Column grow gap={ 2 } justifyContent="end">
<div className="flex flex-col gap-2">
<div className="items-center gap-2">
<LayoutBadgeImageView shrink badgeCode={ selectedBadgeCode } />
<Text>{ LocalizeBadgeName(selectedBadgeCode) }</Text>
<span className="text-sm truncate grow">{ LocalizeBadgeName(selectedBadgeCode) }</span>
</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>
</Column> }
</Column>
</Grid>
<NitroButton
disabled={ !isWearingBadge(selectedBadgeCode) && !canWearBadges() }
onClick={ event => toggleBadge(selectedBadgeCode) }>
{ 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 { FC, MouseEvent, PropsWithChildren, useState } from 'react';
import { attemptBotPlacement, IBotItem, UnseenItemCategory } from '../../../../api';
import { LayoutAvatarImageView, LayoutGridItem } from '../../../../common';
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 =>
export const InventoryBotItemView: FC<PropsWithChildren<{
botItem: IBotItem
}>> = props =>
{
const { botItem = null, children = null, ...rest } = props;
const [ isMouseDown, setMouseDown ] = useState(false);
@ -35,9 +38,9 @@ export const InventoryBotItemView: FC<PropsWithChildren<{ botItem: IBotItem }>>
}
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 } />
{ children }
</LayoutGridItem>
</InfiniteGrid.Item>
);
}

View File

@ -1,18 +1,16 @@
import { GetRoomEngine, IRoomSession, RoomObjectVariable, RoomPreviewer } from '@nitrots/nitro-renderer';
import { FC, useEffect, useState } from 'react';
import { LocalizeText, UnseenItemCategory, attemptBotPlacement } from '../../../../api';
import { AutoGrid, Button, Column, Grid, LayoutRoomPreviewerView, Text } from '../../../../common';
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';
interface InventoryBotViewProps
{
export const InventoryBotView: FC<{
roomSession: IRoomSession;
roomPreviewer: RoomPreviewer;
}
export const InventoryBotView: FC<InventoryBotViewProps> = props =>
}> = props =>
{
const { roomSession = null, roomPreviewer = null } = props;
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') } />;
return (
<Grid>
<Column overflow="hidden" size={ 7 }>
<AutoGrid columnCount={ 5 }>
{ botItems && (botItems.length > 0) && botItems.map(item => <InventoryBotItemView key={ item.botData.id } botItem={ item } />) }
</AutoGrid>
</Column>
<Column overflow="auto" size={ 5 }>
<Column overflow="hidden" position="relative">
<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 } />
</Column>
</div>
{ selectedBot &&
<Column grow gap={ 2 } justifyContent="between">
<Text grow truncate>{ selectedBot.botData.name }</Text>
<div className="flex flex-col justify-between gap-2 grow">
<span className="truncate grow">{ selectedBot.botData.name }</span>
{ !!roomSession &&
<Button variant="success" onClick={ event => attemptBotPlacement(selectedBot) }>
<NitroButton onClick={ event => attemptBotPlacement(selectedBot) }>
{ LocalizeText('inventory.furni.placetoroom') }
</Button> }
</Column> }
</Column>
</Grid>
</NitroButton> }
</div> }
</div>
</div>
);
}

View File

@ -4,7 +4,9 @@ import { GroupItem, attemptItemPlacement } from '../../../../api';
import { useInventoryFurni } from '../../../../hooks';
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 [ isMouseDown, setMouseDown ] = useState(false);

View File

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

View File

@ -2,19 +2,14 @@ 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 { Button, LayoutLimitedEditionCompactPlateView, LayoutRarityLevelView, LayoutRoomPreviewerView } from '../../../../common';
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';
interface InventoryFurnitureViewProps
{
roomSession: IRoomSession;
roomPreviewer: RoomPreviewer;
}
const attemptPlaceMarketplaceOffer = (groupItem: GroupItem) =>
{
const item = groupItem.getLastItem();
@ -26,7 +21,10 @@ const attemptPlaceMarketplaceOffer = (groupItem: GroupItem) =>
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 [ 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') } />;
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">
<InventoryFurnitureSearchView groupItems={ groupItems } setGroupItems={ setFilteredGroupItems } />
<InfiniteGrid<GroupItem>
@ -120,8 +118,8 @@ export const InventoryFurnitureView: FC<InventoryFurnitureViewProps> = props =>
itemRender={ item => <InventoryFurnitureItemView groupItem={ item } /> }
items={ filteredGroupItems } />
</div>
<div className="flex flex-col col-span-5 overflow-hidden">
<div className="relative flex flex-col overflow-hidden">
<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 } /> }
@ -130,16 +128,16 @@ export const InventoryFurnitureView: FC<InventoryFurnitureViewProps> = props =>
</div>
{ selectedItem &&
<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">
{ !!roomSession &&
<Button variant="success" onClick={ event => attemptItemPlacement(selectedItem) }>
<NitroButton onClick={ event => attemptItemPlacement(selectedItem) }>
{ LocalizeText('inventory.furni.placetoroom') }
</Button> }
</NitroButton> }
{ (selectedItem && selectedItem.isSellable) &&
<Button onClick={ event => attemptPlaceMarketplaceOffer(selectedItem) }>
<NitroButton onClick={ event => attemptPlaceMarketplaceOffer(selectedItem) }>
{ LocalizeText('inventory.marketplace.sell') }
</Button> }
</NitroButton> }
</div>
</div> }
</div>

View File

@ -1,8 +1,9 @@
import { MouseEventType } from '@nitrots/nitro-renderer';
import { FC, MouseEvent, PropsWithChildren, useState } from 'react';
import { attemptPetPlacement, IPetItem, UnseenItemCategory } from '../../../../api';
import { LayoutGridItem, LayoutPetImageView } from '../../../../common';
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 =>
{
@ -35,9 +36,9 @@ export const InventoryPetItemView: FC<PropsWithChildren<{ petItem: IPetItem }>>
}
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 } />
{ children }
</LayoutGridItem>
</InfiniteGrid.Item>
);
}

View File

@ -1,18 +1,16 @@
import { GetRoomEngine, IRoomSession, RoomObjectVariable, RoomPreviewer } from '@nitrots/nitro-renderer';
import { FC, useEffect, useState } from 'react';
import { LocalizeText, UnseenItemCategory, attemptPetPlacement } from '../../../../api';
import { AutoGrid, Button, Column, Grid, LayoutRoomPreviewerView, Text } from '../../../../common';
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';
interface InventoryPetViewProps
{
export const InventoryPetView: FC<{
roomSession: IRoomSession;
roomPreviewer: RoomPreviewer;
}
export const InventoryPetView: FC<InventoryPetViewProps> = props =>
}> = props =>
{
const { roomSession = null, roomPreviewer = null } = props;
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') } />;
return (
<Grid>
<Column overflow="hidden" size={ 7 }>
<AutoGrid columnCount={ 5 }>
{ petItems && (petItems.length > 0) && petItems.map(item => <InventoryPetItemView key={ item.petData.id } petItem={ item } />) }
</AutoGrid>
</Column>
<Column overflow="auto" size={ 5 }>
<Column overflow="hidden" position="relative">
<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 } />
</Column>
</div>
{ selectedPet && selectedPet.petData &&
<Column grow gap={ 2 } justifyContent="between">
<Text grow truncate>{ selectedPet.petData.name }</Text>
<div className="flex flex-col justify-between gap-2 grow">
<span className="text-sm truncate grow">{ selectedPet.petData.name }</span>
{ !!roomSession &&
<Button variant="success" onClick={ event => attemptPetPlacement(selectedPet) }>
<NitroButton onClick={ event => attemptPetPlacement(selectedPet) }>
{ LocalizeText('inventory.furni.placetoroom') }
</Button> }
</Column> }
</Column>
</Grid>
</NitroButton> }
</div> }
</div>
</div>
);
}

View File

@ -1,5 +1,4 @@
import { FC } from 'react';
import { Column } from '../../common';
interface LoadingViewProps
{
@ -8,14 +7,38 @@ interface LoadingViewProps
export const LoadingView: FC<LoadingViewProps> = props =>
{
const {} = props;
const generateStars = (count, className) =>
{
return Array.from({ length: count }, () => ({
top: `${ Math.random() * 100 }%`,
left: `${ Math.random() * 100 }%`
})).map((style, index) => (
<div key={ index } className={ className } style={ style }>
<div className="star-part" />
<div className="star-part" />
</div>
));
};
const smallStars = generateStars(90, 'dot');
const mediumStars = generateStars(15, 'star');
return (
<Column fullHeight className="nitro-loading" position="relative">
<div className="container h-100">
<Column fullHeight alignItems="center" justifyContent="end">
<div className="connecting-duck" />
</Column>
<div className="relative w-screen h-screen z-loading bg-loading">
<div className="container flex w-screen h-screen">
<div className="flex items-center justify-center w-screen h-screen">
{ smallStars }
{ mediumStars }
</div>
</div>
</Column>
</div>
/* <div className="relative w-screen h-screen z-loading bg-card-header">
<div className="container flex w-screen h-screen">
<div className="flex items-center justify-center w-screen h-screen">
<div className="connecting-duck" />
</div>
</div>
</div>*/
);
}

View File

@ -75,6 +75,123 @@ body {
}
@layer components {
@keyframes blink {
0%,
100% {
opacity: 1;
}
50% {
opacity: 0;
}
}
@keyframes scale {
0%,
100% {
transform: scale(1);
}
50% {
transform: scale(1.5);
}
}
.dot {
position: absolute;
width: 2px;
height: 2px;
background-color: white;
animation: blink 2s infinite;
}
.star {
position: absolute;
width: 10px;
height: 10px;
color: #ffffff;
&:after {
content: "";
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: currentColor;
width: 5px;
height: 5px;
-webkit-animation: blink 1s linear infinite;
animation: blink 1s linear infinite;
}
.star-part {
position: absolute;
background-color: currentColor;
&:nth-child(1) {
top: 0;
left: 50%;
width: 1px;
height: 100%;
transform: translateX(-50%);
}
&:nth-child(2) {
top: 50%;
left: 0;
width: 100%;
height: 1px;
transform: translateY(-50%);
}
}
}
.connecting-duck {
@apply absolute inset-0 m-auto;
background: url("@/assets/images/loading/connecting-duck-spritesheet.png")
no-repeat top left;
width: 235px;
height: 127px;
animation: connecting-duck 1.5s infinite step-end;
transform: scale(0.5);
}
@keyframes connecting-duck {
0% {
background-position: 0 0;
width: 235px;
height: 127px;
}
15% {
background-position: 0 -132px;
width: 280px;
height: 151px;
}
30% {
background-position: 0 -288px;
width: 326px;
height: 174px;
}
45% {
background-position: 0 -467px;
width: 235px;
height: 127px;
}
60% {
background-position: 0 -599px;
width: 280px;
height: 151px;
}
75% {
background-position: 0 -755px;
width: 326px;
height: 174px;
}
90% {
background-position: 0 -934px;
width: 190px;
height: 104px;
}
}
.avatar-image {
@apply pointer-events-none relative h-[130px] w-[90px] bg-[center_-8px] bg-no-repeat;
}
@ -277,6 +394,11 @@ body {
background-image: url("@/assets/images/chat/styles-icon.png");
width: 17px;
height: 19px;
filter: grayscale(100%);
&:hover {
filter: grayscale(0%);
}
}
&.pencil-icon {
@ -709,3 +831,147 @@ body {
}
}
}
.unique-item {
.unique-bg-override {
@apply z-[2] bg-center bg-no-repeat;
}
&:before {
@apply absolute z-[1] size-full bg-center bg-no-repeat [content:""];
background-image: url("@/assets/images/unique/grid-bg.png");
}
&:after {
@apply absolute bottom-0 z-[4] size-full bg-center bg-no-repeat [content:""];
background-image: url("@/assets/images/unique/grid-bg-glass.png");
}
&.sold-out:after {
@apply bg-center bg-no-repeat;
background-image: url("@/assets/images/unique/grid-bg-sold-out.png"),
url("@/assets/images/unique/grid-bg-glass.png");
}
.unique-item-counter {
background-image: url("@/assets/images/unique/grid-count-bg.png");
@apply bottom-[1px] z-[3] mx-auto my-0 flex h-[9px] w-full items-center justify-center bg-center bg-no-repeat;
}
}
.unique-sold-out-blocker {
width: 364px;
height: 30px;
background: url("@/assets/images/unique/catalog-info-sold-out.png");
div {
float: right;
width: 140px;
text-align: center;
font-weight: bold;
margin-top: 5px;
margin-right: 17px;
color: #000;
}
}
.unique-compact-plate {
display: flex;
flex-direction: column;
justify-content: flex-end;
align-items: center;
right: 16px;
width: 34px;
height: 37px;
background: url("@/assets/images/unique/inventory-info-amount-bg.png");
div {
display: flex;
justify-content: center;
align-items: center;
height: 9.5px;
}
}
.unique-complete-plate {
width: 170px;
height: 29px;
background: url("@/assets/images/unique/catalog-info-amount-bg.png")
no-repeat center;
z-index: 1;
padding-top: 3px;
.plate-container {
margin-left: 45px;
width: 100px;
font-size: 10px;
color: black;
> :first-child {
margin-bottom: 2px;
}
}
}
.limited-edition-number {
display: inline-block;
outline: 0;
height: 5px;
margin-right: 1px;
background-image: url("@/assets/images/unique/numbers.png");
background-repeat: no-repeat;
&:last-child {
margin-right: 0px;
}
&.n-0 {
width: 4px;
background-position: -1px 0px;
}
&.n-1 {
width: 2px;
background-position: -6px 0px;
}
&.n-2 {
width: 4px;
background-position: -9px 0px;
}
&.n-3 {
width: 4px;
background-position: -14px 0px;
}
&.n-4 {
width: 4px;
background-position: -19px 0px;
}
&.n-5 {
width: 4px;
background-position: -24px 0px;
}
&.n-6 {
width: 4px;
background-position: -29px 0px;
}
&.n-7 {
width: 4px;
background-position: -34px 0px;
}
&.n-8 {
width: 4px;
background-position: -39px 0px;
}
&.n-9 {
width: 4px;
background-position: -44px 0px;
}
}

View File

@ -1,6 +1,7 @@
import { useVirtualizer } from '@tanstack/react-virtual';
import { DetailedHTMLProps, Fragment, HTMLAttributes, ReactElement, forwardRef, useEffect, useRef, useState } from 'react';
import { classNames } from './classNames';
import { NitroLimitedEditionStyledNumberView } from './limited-edition';
import { styleNames } from './styleNames';
type Props<T> = {
@ -68,9 +69,10 @@ const InfiniteGridRoot = <T,>(props: Props<T>) =>
<div
key={ virtualRow.key + 'a' }
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 }
style={ {
height: virtualRow.size,
transform: `translateY(${ virtualRow.start }px)`
} }>
{ Array.from(Array(columnCount)).map((e,i) =>
@ -141,8 +143,11 @@ const InfiniteGridItem = forwardRef<HTMLDivElement, {
ref={ ref }
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',
(!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',
(itemUniqueSoldout || (itemUniqueNumber > 0)) && 'unique-item',
itemUniqueSoldout && 'sold-out',
itemUnseen && ' bg-green-500 bg-opacity-40',
className
) }
style={ styleNames(
@ -152,6 +157,19 @@ const InfiniteGridItem = forwardRef<HTMLDivElement, {
style
) }
{ ...rest }>
{ (itemCount > itemCountMinimum) &&
<div className="absolute align-middle rounded bg-red-700 bg-opacity-80 text-white border-black border top-[2px] right-[2px] text-[9.5px] p-[2px] z-[1] leading-[8px]">{ itemCount }</div> }
{ (itemUniqueNumber > 0) &&
<>
<div
className="size-full unique-bg-override"
style={ {
backgroundImage: `url(${ backgroundImageUrl })`
} } />
<div className="absolute bottom-0 unique-item-counter">
<NitroLimitedEditionStyledNumberView value={ itemUniqueNumber } />
</div>
</> }
{ children }
</div>
);

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';

View File

@ -13,7 +13,7 @@ const NitroCardRoot = forwardRef<HTMLDivElement, PropsWithChildren<{
<div
ref={ ref }
className={ classNames(
'flex flex-col rounded-md shadow border border-card-border overflow-hidden min-w-full min-h-full max-w-full max-h-full',
'flex flex-col rounded-md shadow border-2 border-card-border overflow-hidden min-w-full min-h-full max-w-full max-h-full',
className
) }
{ ...rest } />

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,8 @@
export * from './InfiniteGrid';
export * from './NitroButton';
export * from './NitroCard';
export * from './NitroInput';
export * from './NitroItemCountBadge';
export * from './classNames';
export * from './limited-edition';
export * from './styleNames';

View File

@ -0,0 +1,18 @@
import { FC } from 'react';
export const NitroLimitedEditionStyledNumberView: FC<{
value: number;
}> = props =>
{
const { value = 0 } = props;
return (
<>
{ value.toString().split('').map((number, index) =>
<i
key={ index }
className={ 'limited-edition-number n-' + number } />
) }
</>
);
}

View File

@ -0,0 +1 @@
export * from './NitroLimitedEditionStyledNumberView';

View File

@ -15,10 +15,12 @@ const colors = {
'card-grid-item-active': '#ECECEC',
'card-grid-item-border': '#B6BEC5',
'card-grid-item-border-active': '#FFFFFF',
'loading': '#393A85'
};
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);'
};
@ -30,6 +32,9 @@ module.exports = {
},
colors: generateShades(colors),
boxShadow,
backgroundImage: {
'button-gradient-gray': 'linear-gradient(to bottom, #e2e2e2 50%, #c8c8c8 50%)',
},
spacing: {
'card-header': '33px',
'card-tabs': '33px',
@ -39,8 +44,9 @@ module.exports = {
'inventory-h': '320px'
},
zIndex: {
'toolbar': ''
}
'toolbar': '',
'loading': '100'
},
},
},
safelist: [