mirror of
https://github.com/duckietm/Nitro-V3.git
synced 2026-06-19 15:06:20 +00:00
🆙 Take #3 desktop view catalog is now 100%
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { GetRenderer, GetTicker, NitroTicker, RoomPreviewer, TextureUtils } from '@nitrots/nitro-renderer';
|
import { GetRenderer, GetTicker, NitroLogger, NitroTicker, RoomPreviewer, TextureUtils } from '@nitrots/nitro-renderer';
|
||||||
import { FC, MouseEvent, useEffect, useRef } from 'react';
|
import { FC, MouseEvent, useEffect, useRef } from 'react';
|
||||||
|
|
||||||
export const LayoutRoomPreviewerView: FC<{
|
export const LayoutRoomPreviewerView: FC<{
|
||||||
@@ -8,6 +8,13 @@ export const LayoutRoomPreviewerView: FC<{
|
|||||||
{
|
{
|
||||||
const { roomPreviewer = null, height = 0 } = props;
|
const { roomPreviewer = null, height = 0 } = props;
|
||||||
const elementRef = useRef<HTMLDivElement>(null);
|
const elementRef = useRef<HTMLDivElement>(null);
|
||||||
|
// Latch that disables further renders once Pixi throws inside this
|
||||||
|
// previewer. The crash (e.g. blackhole furni's filter chain that
|
||||||
|
// accesses .alphaMode on a null texture) repeats every animation
|
||||||
|
// frame as long as the ticker keeps firing, flooding the console
|
||||||
|
// and locking the catalog. One catch and we stop trying for the
|
||||||
|
// lifetime of this previewer instance.
|
||||||
|
const renderFailedRef = useRef(false);
|
||||||
|
|
||||||
const onClick = (event: MouseEvent<HTMLDivElement>) =>
|
const onClick = (event: MouseEvent<HTMLDivElement>) =>
|
||||||
{
|
{
|
||||||
@@ -21,37 +28,58 @@ export const LayoutRoomPreviewerView: FC<{
|
|||||||
{
|
{
|
||||||
if(!elementRef) return;
|
if(!elementRef) return;
|
||||||
|
|
||||||
|
renderFailedRef.current = false;
|
||||||
|
|
||||||
const width = elementRef.current.parentElement.clientWidth;
|
const width = elementRef.current.parentElement.clientWidth;
|
||||||
const texture = TextureUtils.createRenderTexture(width, height);
|
const texture = TextureUtils.createRenderTexture(width, height);
|
||||||
|
|
||||||
const paintToDOM = () =>
|
const paintToDOM = () =>
|
||||||
{
|
{
|
||||||
|
if(renderFailedRef.current) return;
|
||||||
if(!roomPreviewer || !elementRef.current) return;
|
if(!roomPreviewer || !elementRef.current) return;
|
||||||
|
|
||||||
const renderingCanvas = roomPreviewer.getRenderingCanvas();
|
const renderingCanvas = roomPreviewer.getRenderingCanvas();
|
||||||
|
|
||||||
if(!renderingCanvas) return;
|
if(!renderingCanvas) return;
|
||||||
|
|
||||||
GetRenderer().render({
|
try
|
||||||
target: texture,
|
{
|
||||||
container: renderingCanvas.master,
|
GetRenderer().render({
|
||||||
clear: true
|
target: texture,
|
||||||
});
|
container: renderingCanvas.master,
|
||||||
|
clear: true
|
||||||
|
});
|
||||||
|
|
||||||
const canvas = GetRenderer().texture.generateCanvas(texture);
|
const canvas = GetRenderer().texture.generateCanvas(texture);
|
||||||
const base64 = canvas.toDataURL('image/png');
|
const base64 = canvas.toDataURL('image/png');
|
||||||
|
|
||||||
canvas.width = 0;
|
canvas.width = 0;
|
||||||
canvas.height = 0;
|
canvas.height = 0;
|
||||||
|
|
||||||
elementRef.current.style.backgroundImage = `url(${ base64 })`;
|
elementRef.current.style.backgroundImage = `url(${ base64 })`;
|
||||||
|
}
|
||||||
|
catch(error)
|
||||||
|
{
|
||||||
|
renderFailedRef.current = true;
|
||||||
|
NitroLogger.error('LayoutRoomPreviewerView paint failed; disabling further renders for this preview', error);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const update = (ticker: NitroTicker) =>
|
const update = (ticker: NitroTicker) =>
|
||||||
{
|
{
|
||||||
|
if(renderFailedRef.current) return;
|
||||||
if(!roomPreviewer || !elementRef.current) return;
|
if(!roomPreviewer || !elementRef.current) return;
|
||||||
|
|
||||||
roomPreviewer.updatePreviewRoomView();
|
try
|
||||||
|
{
|
||||||
|
roomPreviewer.updatePreviewRoomView();
|
||||||
|
}
|
||||||
|
catch(error)
|
||||||
|
{
|
||||||
|
renderFailedRef.current = true;
|
||||||
|
NitroLogger.error('LayoutRoomPreviewerView update failed; disabling further renders for this preview', error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const renderingCanvas = roomPreviewer.getRenderingCanvas();
|
const renderingCanvas = roomPreviewer.getRenderingCanvas();
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { ColorConverter } from '@nitrots/nitro-renderer';
|
import { ColorConverter } from '@nitrots/nitro-renderer';
|
||||||
import { FC, useMemo, useState } from 'react';
|
import { FC, useMemo, useState } from 'react';
|
||||||
import { FaFillDrip } from 'react-icons/fa';
|
import { FaExchangeAlt, FaFillDrip, FaSyncAlt } from 'react-icons/fa';
|
||||||
import { IPurchasableOffer, SanitizeHtml } from '../../../../../api';
|
import { IPurchasableOffer, ProductTypeEnum, SanitizeHtml } from '../../../../../api';
|
||||||
import { AutoGrid, Button, Column, Grid, LayoutGridItem, Text } from '../../../../../common';
|
import { AutoGrid, Button, Column, LayoutGridItem, Text } from '../../../../../common';
|
||||||
import { useCatalogData, useCatalogUiState } from '../../../../../hooks';
|
import { useCatalogData, useCatalogUiState } from '../../../../../hooks';
|
||||||
import { CatalogGridOfferView } from '../common/CatalogGridOfferView';
|
import { CatalogGridOfferView } from '../common/CatalogGridOfferView';
|
||||||
import { CatalogAddOnBadgeWidgetView } from '../widgets/CatalogAddOnBadgeWidgetView';
|
import { CatalogAddOnBadgeWidgetView } from '../widgets/CatalogAddOnBadgeWidgetView';
|
||||||
@@ -22,7 +22,7 @@ export const CatalogLayoutColorGroupingView: FC<CatalogLayoutColorGroupViewProps
|
|||||||
{
|
{
|
||||||
const { page = null } = props;
|
const { page = null } = props;
|
||||||
const [ colorableItems, setColorableItems ] = useState<Map<string, number[]>>(new Map<string, number[]>());
|
const [ colorableItems, setColorableItems ] = useState<Map<string, number[]>>(new Map<string, number[]>());
|
||||||
const { currentOffer = null } = useCatalogData();
|
const { currentOffer = null, roomPreviewer = null } = useCatalogData();
|
||||||
const { setCurrentOffer = null } = useCatalogUiState();
|
const { setCurrentOffer = null } = useCatalogUiState();
|
||||||
const [ colorsShowing, setColorsShowing ] = useState<boolean>(false);
|
const [ colorsShowing, setColorsShowing ] = useState<boolean>(false);
|
||||||
|
|
||||||
@@ -132,46 +132,59 @@ export const CatalogLayoutColorGroupingView: FC<CatalogLayoutColorGroupViewProps
|
|||||||
}, [ page.offers ]);
|
}, [ page.offers ]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid>
|
<Column overflow="hidden">
|
||||||
<Column overflow="hidden" size={ 7 }>
|
{ /* Top: two visible rows of furni tiles. Tile is 70px tall
|
||||||
<AutoGrid columnCount={ 5 }>
|
and the AutoGrid handles its own overflow if there are
|
||||||
{ (!colorsShowing || !currentOffer || !colorableItems.has(currentOffer.product.furnitureData.className)) &&
|
more than two rows worth of offers. */ }
|
||||||
offers.map((offer, index) => <CatalogGridOfferView key={ index } itemActive={ (currentOffer && (currentOffer.product.furnitureData.hasIndexedColor ? currentOffer.product.furnitureData.className === offer.product.furnitureData.className : currentOffer.offerId === offer.offerId)) } offer={ offer } selectOffer={ selectOffer } />)
|
<div className="shrink-0" style={ { maxHeight: 154 } }>
|
||||||
}
|
{ (!colorsShowing || !currentOffer || !colorableItems.has(currentOffer.product.furnitureData.className)) &&
|
||||||
{ (colorsShowing && currentOffer && colorableItems.has(currentOffer.product.furnitureData.className)) &&
|
<AutoGrid columnCount={ 7 } columnMinHeight={ 70 } columnMinWidth={ 45 }>
|
||||||
colorableItems.get(currentOffer.product.furnitureData.className).map((color, index) => <LayoutGridItem key={ index } itemHighlight className="clear-bg" itemActive={ (currentOffer.product.furnitureData.colorIndex === index) } itemColor={ ColorConverter.int2rgb(color) } onClick={ event => selectColor(index, currentOffer.product.furnitureData.className) } />)
|
{ offers.map((offer, index) => <CatalogGridOfferView key={ index } itemActive={ (currentOffer && (currentOffer.product.furnitureData.hasIndexedColor ? currentOffer.product.furnitureData.className === offer.product.furnitureData.className : currentOffer.offerId === offer.offerId)) } offer={ offer } selectOffer={ selectOffer } />) }
|
||||||
}
|
</AutoGrid> }
|
||||||
</AutoGrid>
|
{ (colorsShowing && currentOffer && colorableItems.has(currentOffer.product.furnitureData.className)) &&
|
||||||
</Column>
|
<div className="nitro-catalog-classic-color-swatches flex flex-wrap gap-1 p-2 overflow-auto">
|
||||||
<Column center={ !currentOffer } overflow="hidden" size={ 5 }>
|
{ colorableItems.get(currentOffer.product.furnitureData.className).map((color, index) => <LayoutGridItem key={ index } itemHighlight className="clear-bg" itemActive={ (currentOffer.product.furnitureData.colorIndex === index) } itemColor={ ColorConverter.int2rgb(color) } onClick={ event => selectColor(index, currentOffer.product.furnitureData.className) } />) }
|
||||||
{ !currentOffer &&
|
</div> }
|
||||||
<>
|
</div>
|
||||||
{ !!page.localization.getImage(1) && <img alt="" src={ page.localization.getImage(1) } /> }
|
|
||||||
<Text center dangerouslySetInnerHTML={ { __html: SanitizeHtml(page.localization.getText(0)) } } />
|
{ /* Bottom: preview pane stacked under the grid. Mirrors the
|
||||||
</> }
|
default-3x3 split (preview on the left, offer info on the
|
||||||
{ currentOffer &&
|
right) so the rotate/state buttons and Buy/Gift actions
|
||||||
<>
|
sit where the user expects. */ }
|
||||||
<div className="relative overflow-hidden">
|
{ !currentOffer &&
|
||||||
<CatalogViewProductWidgetView />
|
<Column center grow overflow="hidden">
|
||||||
<CatalogAddOnBadgeWidgetView className="bg-muted rounded bottom-1 inset-e-1" position="absolute" />
|
{ !!page.localization.getImage(1) && <img alt="" src={ page.localization.getImage(1) } /> }
|
||||||
{ currentOffer.product.furnitureData.hasIndexedColor &&
|
<Text center dangerouslySetInnerHTML={ { __html: SanitizeHtml(page.localization.getText(0)) } } />
|
||||||
<Button className="bottom-1 inset-s-1" position="absolute" onClick={ event => setColorsShowing(prev => !prev) }>
|
</Column> }
|
||||||
<FaFillDrip className="fa-icon" />
|
{ currentOffer &&
|
||||||
</Button> }
|
<div className="nitro-catalog-classic-offer-panel flex flex-col items-center grow overflow-hidden gap-2">
|
||||||
|
<div className="nitro-catalog-classic-offer-preview relative flex items-center justify-center overflow-hidden">
|
||||||
|
{ (currentOffer.product.productType !== ProductTypeEnum.BADGE) &&
|
||||||
|
<>
|
||||||
|
<button className="nitro-catalog-classic-preview-btn nitro-catalog-classic-preview-rotate" onClick={ () => roomPreviewer?.changeRoomObjectDirection() }>
|
||||||
|
<FaSyncAlt />
|
||||||
|
</button>
|
||||||
|
<button className="nitro-catalog-classic-preview-btn nitro-catalog-classic-preview-state" onClick={ () => roomPreviewer?.changeRoomObjectState() }>
|
||||||
|
<FaExchangeAlt />
|
||||||
|
</button>
|
||||||
|
</> }
|
||||||
|
<CatalogViewProductWidgetView />
|
||||||
|
<CatalogAddOnBadgeWidgetView className="bg-muted rounded bottom-1 inset-e-1" position="absolute" />
|
||||||
|
{ currentOffer.product.furnitureData.hasIndexedColor &&
|
||||||
|
<Button className="bottom-1 inset-s-1" position="absolute" onClick={ event => setColorsShowing(prev => !prev) }>
|
||||||
|
<FaFillDrip className="fa-icon" />
|
||||||
|
</Button> }
|
||||||
|
</div>
|
||||||
|
<div className="w-full max-w-[360px] flex flex-col gap-2 px-1">
|
||||||
|
<CatalogLimitedItemWidgetView />
|
||||||
|
<Text truncate>{ currentOffer.localizationName }</Text>
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<CatalogSpinnerWidgetView />
|
||||||
|
<CatalogTotalPriceWidget alignItems="end" justifyContent="end" />
|
||||||
</div>
|
</div>
|
||||||
<Column className="grow!" gap={ 1 }>
|
<CatalogPurchaseWidgetView />
|
||||||
<CatalogLimitedItemWidgetView />
|
</div>
|
||||||
<Text truncate className="grow!">{ currentOffer.localizationName }</Text>
|
</div> }
|
||||||
<div className="flex justify-between">
|
</Column>
|
||||||
<div className="flex flex-col gap-1">
|
|
||||||
<CatalogSpinnerWidgetView />
|
|
||||||
</div>
|
|
||||||
<CatalogTotalPriceWidget alignItems="end" justifyContent="end" />
|
|
||||||
</div>
|
|
||||||
<CatalogPurchaseWidgetView />
|
|
||||||
</Column>
|
|
||||||
</> }
|
|
||||||
</Column>
|
|
||||||
</Grid>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -291,7 +291,7 @@ export const CatalogLayoutPetView: FC<CatalogLayoutProps> = props =>
|
|||||||
{ LocalizeText('catalog.pets.back.breeds') }
|
{ LocalizeText('catalog.pets.back.breeds') }
|
||||||
</button> }
|
</button> }
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-6 gap-1">
|
<div className={ colorsShowing ? 'nitro-catalog-classic-color-swatches flex flex-wrap gap-1 p-2 overflow-auto' : 'grid grid-cols-6 gap-1' }>
|
||||||
{ !colorsShowing && (sellablePalettes.length > 0) && sellablePalettes.map((palette, index) => (
|
{ !colorsShowing && (sellablePalettes.length > 0) && sellablePalettes.map((palette, index) => (
|
||||||
<LayoutGridItem
|
<LayoutGridItem
|
||||||
key={ index }
|
key={ index }
|
||||||
@@ -303,10 +303,12 @@ export const CatalogLayoutPetView: FC<CatalogLayoutProps> = props =>
|
|||||||
</LayoutGridItem>
|
</LayoutGridItem>
|
||||||
)) }
|
)) }
|
||||||
{ colorsShowing && (sellableColors.length > 0) && sellableColors.map((colorSet, index) => (
|
{ colorsShowing && (sellableColors.length > 0) && sellableColors.map((colorSet, index) => (
|
||||||
<div
|
<LayoutGridItem
|
||||||
key={ index }
|
key={ index }
|
||||||
className={ `w-full aspect-square rounded border-2 cursor-pointer transition-all ${ selectedColorIndex === index ? 'border-primary scale-110 shadow-md' : 'border-card-grid-item-border hover:border-primary/50' }` }
|
itemHighlight
|
||||||
style={ { backgroundColor: `#${ ColorConverter.int2rgb(colorSet[0]) }` } }
|
className="clear-bg"
|
||||||
|
itemActive={ (selectedColorIndex === index) }
|
||||||
|
itemColor={ ColorConverter.int2rgb(colorSet[0]) }
|
||||||
onClick={ () => setSelectedColorIndex(index) }
|
onClick={ () => setSelectedColorIndex(index) }
|
||||||
/>
|
/>
|
||||||
)) }
|
)) }
|
||||||
|
|||||||
@@ -22,85 +22,98 @@ export const CatalogViewProductWidgetView: FC<{}> = props =>
|
|||||||
roomPreviewer.updateObjectRoom('default', 'default', 'default');
|
roomPreviewer.updateObjectRoom('default', 'default', 'default');
|
||||||
roomPreviewer.updateRoomWallsAndFloorVisibility(true, true);
|
roomPreviewer.updateRoomWallsAndFloorVisibility(true, true);
|
||||||
|
|
||||||
switch(product.productType)
|
const populate = () =>
|
||||||
{
|
{
|
||||||
case ProductTypeEnum.FLOOR: {
|
switch(product.productType)
|
||||||
if(!product.furnitureData) return;
|
{
|
||||||
|
case ProductTypeEnum.FLOOR: {
|
||||||
|
if(!product.furnitureData) return;
|
||||||
|
|
||||||
const furniData = GetSessionDataManager().getFloorItemData(product.furnitureData.id);
|
const furniData = GetSessionDataManager().getFloorItemData(product.furnitureData.id);
|
||||||
const isPurchasableClothing = (product.furnitureData.specialType === FurniCategory.FIGURE_PURCHASABLE_SET);
|
const isPurchasableClothing = (product.furnitureData.specialType === FurniCategory.FIGURE_PURCHASABLE_SET);
|
||||||
const hasResolvableFigureSets = (() =>
|
const hasResolvableFigureSets = (() =>
|
||||||
{
|
|
||||||
if(!furniData || !furniData.customParams || !furniData.customParams.length) return false;
|
|
||||||
|
|
||||||
const parts = furniData.customParams.split(',').map(value => parseInt(value));
|
|
||||||
|
|
||||||
for(const part of parts)
|
|
||||||
{
|
{
|
||||||
if(isNaN(part)) continue;
|
if(!furniData || !furniData.customParams || !furniData.customParams.length) return false;
|
||||||
|
|
||||||
if(GetAvatarRenderManager().structureData?.getFigurePartSet(part)) return true;
|
const parts = furniData.customParams.split(',').map(value => parseInt(value));
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
for(const part of parts)
|
||||||
})();
|
{
|
||||||
|
if(isNaN(part)) continue;
|
||||||
|
|
||||||
if(isPurchasableClothing || hasResolvableFigureSets)
|
if(GetAvatarRenderManager().structureData?.getFigurePartSet(part)) return true;
|
||||||
{
|
}
|
||||||
const customParts = furniData.customParams.split(',').map(value => parseInt(value));
|
|
||||||
const figureSets: number[] = [];
|
|
||||||
|
|
||||||
for(const part of customParts)
|
return false;
|
||||||
|
})();
|
||||||
|
|
||||||
|
if(isPurchasableClothing || hasResolvableFigureSets)
|
||||||
{
|
{
|
||||||
if(isNaN(part)) continue;
|
const customParts = furniData.customParams.split(',').map(value => parseInt(value));
|
||||||
|
const figureSets: number[] = [];
|
||||||
|
|
||||||
if(GetAvatarRenderManager().isValidFigureSetForGender(part, GetSessionDataManager().gender)) figureSets.push(part);
|
for(const part of customParts)
|
||||||
|
{
|
||||||
|
if(isNaN(part)) continue;
|
||||||
|
|
||||||
|
if(GetAvatarRenderManager().isValidFigureSetForGender(part, GetSessionDataManager().gender)) figureSets.push(part);
|
||||||
|
}
|
||||||
|
|
||||||
|
const figureString = BuildPurchasableClothingFigure(GetSessionDataManager().figure, figureSets);
|
||||||
|
|
||||||
|
roomPreviewer.addAvatarIntoRoom(figureString, product.productClassId);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
const figureString = BuildPurchasableClothingFigure(GetSessionDataManager().figure, figureSets);
|
{
|
||||||
|
roomPreviewer.addFurnitureIntoRoom(product.productClassId, new Vector3d(90), previewStuffData, product.extraParam);
|
||||||
roomPreviewer.addAvatarIntoRoom(figureString, product.productClassId);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
roomPreviewer.addFurnitureIntoRoom(product.productClassId, new Vector3d(90), previewStuffData, product.extraParam);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
case ProductTypeEnum.WALL: {
|
|
||||||
if(!product.furnitureData) return;
|
|
||||||
|
|
||||||
roomPreviewer.updateRoomWallsAndFloorVisibility(true, true);
|
|
||||||
|
|
||||||
switch(product.furnitureData.specialType)
|
|
||||||
{
|
|
||||||
case FurniCategory.FLOOR:
|
|
||||||
roomPreviewer.updateObjectRoom(product.extraParam);
|
|
||||||
return;
|
|
||||||
case FurniCategory.WALL_PAPER:
|
|
||||||
roomPreviewer.updateObjectRoom(null, product.extraParam);
|
|
||||||
return;
|
|
||||||
case FurniCategory.LANDSCAPE: {
|
|
||||||
roomPreviewer.updateObjectRoom(null, null, product.extraParam);
|
|
||||||
|
|
||||||
const furniData = GetSessionDataManager().getWallItemDataByName('window_double_default');
|
|
||||||
|
|
||||||
if(furniData) roomPreviewer.addWallItemIntoRoom(furniData.id, new Vector3d(90), furniData.customParams);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
default:
|
return;
|
||||||
roomPreviewer.updateObjectRoom('default', 'default', 'default');
|
|
||||||
roomPreviewer.addWallItemIntoRoom(product.productClassId, new Vector3d(90), product.extraParam);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
case ProductTypeEnum.WALL: {
|
||||||
|
if(!product.furnitureData) return;
|
||||||
|
|
||||||
|
roomPreviewer.updateRoomWallsAndFloorVisibility(true, true);
|
||||||
|
|
||||||
|
switch(product.furnitureData.specialType)
|
||||||
|
{
|
||||||
|
case FurniCategory.FLOOR:
|
||||||
|
roomPreviewer.updateObjectRoom(product.extraParam);
|
||||||
|
return;
|
||||||
|
case FurniCategory.WALL_PAPER:
|
||||||
|
roomPreviewer.updateObjectRoom(null, product.extraParam);
|
||||||
|
return;
|
||||||
|
case FurniCategory.LANDSCAPE: {
|
||||||
|
roomPreviewer.updateObjectRoom(null, null, product.extraParam);
|
||||||
|
|
||||||
|
const furniData = GetSessionDataManager().getWallItemDataByName('window_double_default');
|
||||||
|
|
||||||
|
if(furniData) roomPreviewer.addWallItemIntoRoom(furniData.id, new Vector3d(90), furniData.customParams);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
roomPreviewer.updateObjectRoom('default', 'default', 'default');
|
||||||
|
roomPreviewer.addWallItemIntoRoom(product.productClassId, new Vector3d(90), product.extraParam);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case ProductTypeEnum.ROBOT:
|
||||||
|
roomPreviewer.addAvatarIntoRoom(product.extraParam, 0);
|
||||||
|
return;
|
||||||
|
case ProductTypeEnum.EFFECT:
|
||||||
|
roomPreviewer.addAvatarIntoRoom(GetSessionDataManager().figure, product.productClassId);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
case ProductTypeEnum.ROBOT:
|
};
|
||||||
roomPreviewer.addAvatarIntoRoom(product.extraParam, 0);
|
|
||||||
return;
|
populate();
|
||||||
case ProductTypeEnum.EFFECT:
|
|
||||||
roomPreviewer.addAvatarIntoRoom(GetSessionDataManager().figure, product.productClassId);
|
// RoomPreviewer.addFurnitureIntoRoom / addAvatarIntoRoom flip
|
||||||
return;
|
// _automaticStateChange to true, which makes the ticker advance
|
||||||
}
|
// the room object's state every AUTOMATIC_STATE_CHANGE_INTERVAL.
|
||||||
|
// In the catalog we want the preview to sit still until the
|
||||||
|
// user clicks the state button explicitly - turn it back off
|
||||||
|
// after populate() runs.
|
||||||
|
roomPreviewer.setAutomaticStateChange(false);
|
||||||
}, [ currentOffer, previewStuffData, roomPreviewer ]);
|
}, [ currentOffer, previewStuffData, roomPreviewer ]);
|
||||||
|
|
||||||
if(!currentOffer) return null;
|
if(!currentOffer) return null;
|
||||||
@@ -119,5 +132,11 @@ export const CatalogViewProductWidgetView: FC<{}> = props =>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return <LayoutRoomPreviewerView height={ 240 } roomPreviewer={ roomPreviewer } />;
|
// Re-mount the previewer whenever the offer changes so the render
|
||||||
|
// latch / texture handle in LayoutRoomPreviewerView resets cleanly.
|
||||||
|
// Without this a single broken offer (e.g. blackhole's Pixi filter
|
||||||
|
// crash) latches the previewer permanently and every following
|
||||||
|
// offer paints nothing - the singleton roomPreviewer + 240px height
|
||||||
|
// keep the same component mounted otherwise.
|
||||||
|
return <LayoutRoomPreviewerView key={ currentOffer?.offerId } height={ 240 } roomPreviewer={ roomPreviewer } />;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -12,14 +12,6 @@
|
|||||||
--catalog-swf-select-outer: #82d1ed;
|
--catalog-swf-select-outer: #82d1ed;
|
||||||
--catalog-swf-bc: #ff8d00;
|
--catalog-swf-bc: #ff8d00;
|
||||||
--catalog-swf-bc-outer: #ffb53c;
|
--catalog-swf-bc-outer: #ffb53c;
|
||||||
--habbo-skin-ubuntu: url("../../assets/images/catalog/swf/skins/habbo_skin_ubuntu.png");
|
|
||||||
--habbo-skin-blue: url("../../assets/images/catalog/swf/skins/habbo_skin_blue.png");
|
|
||||||
--habbo-skin-illumina-light: url("../../assets/images/catalog/swf/skins/habbo_skin_illumina_light.png");
|
|
||||||
--habbo-skin-illumina-dark: url("../../assets/images/catalog/swf/skins/habbo_skin_illumina_dark.png");
|
|
||||||
--habbo-slice-frame: url("../../assets/images/catalog/swf/ubuntu_frame3_26x55.png");
|
|
||||||
--habbo-slice-tab-default: url("../../assets/images/catalog/swf/ubuntu_tab3_default_22x32.png");
|
|
||||||
--habbo-slice-tab-selected: url("../../assets/images/catalog/swf/ubuntu_tab3_selected_22x32.png");
|
|
||||||
--habbo-slice-tab-hover: url("../../assets/images/catalog/swf/ubuntu_tab3_hover_22x32.png");
|
|
||||||
/* Light gray secondary button - cropped from catalog_skin1.png
|
/* Light gray secondary button - cropped from catalog_skin1.png
|
||||||
at (10, 190, 25x22). Drives the gift button "Cadeau", the
|
at (10, 190, 25x22). Drives the gift button "Cadeau", the
|
||||||
preview-room control button and the generic .nitro-catalog-swf-
|
preview-room control button and the generic .nitro-catalog-swf-
|
||||||
@@ -42,10 +34,6 @@
|
|||||||
--habbo-button-green-hover: url("../../assets/images/catalog/buttons/buy_hover.png");
|
--habbo-button-green-hover: url("../../assets/images/catalog/buttons/buy_hover.png");
|
||||||
--habbo-button-green-pressed: url("../../assets/images/catalog/buttons/buy_pressed.png");
|
--habbo-button-green-pressed: url("../../assets/images/catalog/buttons/buy_pressed.png");
|
||||||
--habbo-button-green-disabled: url("../../assets/images/catalog/buttons/buy_disabled.png");
|
--habbo-button-green-disabled: url("../../assets/images/catalog/buttons/buy_disabled.png");
|
||||||
--habbo-grid-default: url("../../assets/images/catalog/swf/habbo_grid.png");
|
|
||||||
--habbo-grid-hover: url("../../assets/images/catalog/swf/habbo_grid_hover.png");
|
|
||||||
--habbo-grid-selected: url("../../assets/images/catalog/swf/habbo_grid_selected.png");
|
|
||||||
--habbo-grid-selected-inactive: url("../../assets/images/catalog/swf/habbo_grid_selected_inactive.png");
|
|
||||||
--habbo-close: url("../../assets/images/catalog/buttons/close.png");
|
--habbo-close: url("../../assets/images/catalog/buttons/close.png");
|
||||||
--habbo-close-hover: url("../../assets/images/catalog/buttons/close_hover.png");
|
--habbo-close-hover: url("../../assets/images/catalog/buttons/close_hover.png");
|
||||||
--habbo-close-pressed: url("../../assets/images/catalog/buttons/close_pressed.png");
|
--habbo-close-pressed: url("../../assets/images/catalog/buttons/close_pressed.png");
|
||||||
@@ -782,14 +770,24 @@
|
|||||||
|
|
||||||
.nitro-catalog-classic-offer-preview {
|
.nitro-catalog-classic-offer-preview {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 360px;
|
|
||||||
min-width: 360px;
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background: #000;
|
background: #000;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* The default-3x3 layout puts the preview next to .offer-info inside
|
||||||
|
.offer-panel and needs the 360px column. Scope the pin to that
|
||||||
|
context so other layouts (color-grouping, etc.) can put the same
|
||||||
|
preview class inside a flex/grid column and let it track the
|
||||||
|
container width. Without this scoping the absolute-positioned
|
||||||
|
rotate/state buttons sit past the column's right edge and get
|
||||||
|
clipped by overflow: hidden. */
|
||||||
|
.nitro-catalog-classic-offer-panel > .nitro-catalog-classic-offer-preview {
|
||||||
|
width: 360px;
|
||||||
|
min-width: 360px;
|
||||||
|
}
|
||||||
|
|
||||||
.nitro-catalog-classic-preview-title {
|
.nitro-catalog-classic-preview-title {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 12px;
|
top: 12px;
|
||||||
@@ -892,12 +890,19 @@
|
|||||||
min-height: var(--nitro-grid-column-min-height, 70px) !important;
|
min-height: var(--nitro-grid-column-min-height, 70px) !important;
|
||||||
border: 0 !important;
|
border: 0 !important;
|
||||||
border-radius: 0 !important;
|
border-radius: 0 !important;
|
||||||
background-color: transparent !important;
|
|
||||||
background-image: none !important;
|
background-image: none !important;
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
overflow: visible !important;
|
overflow: visible !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Furni tiles drive their look from the icon image and need a clear
|
||||||
|
background. Color-grouping swatches use itemHighlight (.has-highlight)
|
||||||
|
to ask LayoutGridItem for a solid colour via inline backgroundColor -
|
||||||
|
keep the transparent override off those so the swatch is visible. */
|
||||||
|
.nitro-catalog-classic-window .layout-grid-item:not(.has-highlight) {
|
||||||
|
background-color: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
.nitro-catalog-classic-window .layout-grid-item:hover {
|
.nitro-catalog-classic-window .layout-grid-item:hover {
|
||||||
background-image: none !important;
|
background-image: none !important;
|
||||||
box-shadow: inset 0 0 0 1px #a1a19b !important;
|
box-shadow: inset 0 0 0 1px #a1a19b !important;
|
||||||
@@ -911,6 +916,42 @@
|
|||||||
inset -2px -2px 0 #ecece4 !important;
|
inset -2px -2px 0 #ecece4 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Habbo-classic colour swatches: small chip with a 1px dark border
|
||||||
|
and a subtle inner highlight so light tones still read as buttons.
|
||||||
|
Hover lifts the border; the selected swatch is "pressed" with a
|
||||||
|
sunken inner shadow and a bright cyan ring matching the catalog
|
||||||
|
selection accent. The cream inset from the generic .is-active rule
|
||||||
|
above would wash out the swatch colour, so we replace it here. */
|
||||||
|
.nitro-catalog-classic-window .layout-grid-item.has-highlight {
|
||||||
|
width: 26px !important;
|
||||||
|
height: 26px !important;
|
||||||
|
min-width: 26px !important;
|
||||||
|
min-height: 26px !important;
|
||||||
|
margin: 1px !important;
|
||||||
|
border: 1px solid #2a2a2a !important;
|
||||||
|
border-radius: 2px !important;
|
||||||
|
box-shadow:
|
||||||
|
inset 1px 1px 0 rgba(255, 255, 255, 0.35),
|
||||||
|
inset -1px -1px 0 rgba(0, 0, 0, 0.18) !important;
|
||||||
|
cursor: pointer !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nitro-catalog-classic-window .layout-grid-item.has-highlight:hover {
|
||||||
|
border-color: #000 !important;
|
||||||
|
box-shadow:
|
||||||
|
inset 1px 1px 0 rgba(255, 255, 255, 0.5),
|
||||||
|
inset -1px -1px 0 rgba(0, 0, 0, 0.25),
|
||||||
|
0 0 0 1px rgba(0, 0, 0, 0.45) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nitro-catalog-classic-window .layout-grid-item.has-highlight.is-active {
|
||||||
|
border-color: #000 !important;
|
||||||
|
box-shadow:
|
||||||
|
inset 0 0 0 2px #ffffff,
|
||||||
|
inset 0 0 0 3px #000,
|
||||||
|
0 0 0 1px #63c5e9 !important;
|
||||||
|
}
|
||||||
|
|
||||||
.nitro-catalog-classic-grid-offer-icon {
|
.nitro-catalog-classic-grid-offer-icon {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
@@ -1432,6 +1473,44 @@
|
|||||||
right: 6px;
|
right: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Bulletproof override for the rotate/state buttons. The shared SWF
|
||||||
|
button rule above lays a transparent body + border-image skin on
|
||||||
|
top, which works only when the catalog/buttons/btn_secondary*.png
|
||||||
|
sprites resolve - if they're missing the button renders 0x0
|
||||||
|
invisible. Pin the box and paint a visible gradient + outline so
|
||||||
|
the controls are always discoverable, and force z-index above the
|
||||||
|
room-previewer DIV so they sit on top of the rendered scene. */
|
||||||
|
.nitro-catalog-classic-window button.nitro-catalog-classic-preview-btn {
|
||||||
|
width: 28px !important;
|
||||||
|
height: 26px !important;
|
||||||
|
min-width: 28px !important;
|
||||||
|
min-height: 26px !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
border: 1px solid #2a2a2a !important;
|
||||||
|
border-image: none !important;
|
||||||
|
border-image-source: none !important;
|
||||||
|
border-radius: 3px !important;
|
||||||
|
background: linear-gradient(180deg, #f6f6f0 0%, #d3d3c8 100%) !important;
|
||||||
|
background-color: #ecece4 !important;
|
||||||
|
background-image: linear-gradient(180deg, #f6f6f0 0%, #d3d3c8 100%) !important;
|
||||||
|
box-shadow:
|
||||||
|
inset 1px 1px 0 rgba(255, 255, 255, 0.7),
|
||||||
|
0 1px 0 rgba(0, 0, 0, 0.35) !important;
|
||||||
|
z-index: 10 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nitro-catalog-classic-window button.nitro-catalog-classic-preview-btn:hover {
|
||||||
|
background: linear-gradient(180deg, #ffffff 0%, #dedeD2 100%) !important;
|
||||||
|
background-image: linear-gradient(180deg, #ffffff 0%, #dedeD2 100%) !important;
|
||||||
|
border-color: #000 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nitro-catalog-classic-window button.nitro-catalog-classic-preview-btn:active {
|
||||||
|
background: linear-gradient(180deg, #d3d3c8 0%, #f6f6f0 100%) !important;
|
||||||
|
background-image: linear-gradient(180deg, #d3d3c8 0%, #f6f6f0 100%) !important;
|
||||||
|
box-shadow: inset 1px 1px 0 rgba(0, 0, 0, 0.18) !important;
|
||||||
|
}
|
||||||
|
|
||||||
.nitro-catalog-classic-window .nitro-catalog-classic-navigation-shell::-webkit-scrollbar,
|
.nitro-catalog-classic-window .nitro-catalog-classic-navigation-shell::-webkit-scrollbar,
|
||||||
.nitro-catalog-classic-window .nitro-catalog-classic-navigation-list::-webkit-scrollbar,
|
.nitro-catalog-classic-window .nitro-catalog-classic-navigation-list::-webkit-scrollbar,
|
||||||
.nitro-catalog-classic-window .nitro-catalog-classic-grid-shell::-webkit-scrollbar,
|
.nitro-catalog-classic-window .nitro-catalog-classic-grid-shell::-webkit-scrollbar,
|
||||||
|
|||||||
@@ -1,70 +1,3 @@
|
|||||||
.nitro-swf-button {
|
|
||||||
min-height: 22px !important;
|
|
||||||
height: 22px;
|
|
||||||
padding: 2px 10px !important;
|
|
||||||
border: 3px solid transparent !important;
|
|
||||||
border-radius: 0 !important;
|
|
||||||
border-image-source: url("../../assets/images/catalog/swf/habbo_skin_button_default_9x22.png") !important;
|
|
||||||
border-image-slice: 3 3 3 3 fill !important;
|
|
||||||
border-image-width: 3px !important;
|
|
||||||
border-image-repeat: stretch !important;
|
|
||||||
background: transparent !important;
|
|
||||||
background-color: transparent !important;
|
|
||||||
background-image: none !important;
|
|
||||||
box-shadow: none !important;
|
|
||||||
color: #222 !important;
|
|
||||||
font-size: 11px !important;
|
|
||||||
font-weight: 700 !important;
|
|
||||||
line-height: 16px !important;
|
|
||||||
text-shadow: 0 1px 0 rgba(255,255,255,.75) !important;
|
|
||||||
transition: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nitro-swf-button:hover {
|
|
||||||
border-image-source: url("../../assets/images/catalog/swf/habbo_skin_button_hover_9x22.png") !important;
|
|
||||||
background: transparent !important;
|
|
||||||
background-color: transparent !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nitro-swf-button:active,
|
|
||||||
.nitro-swf-button.active {
|
|
||||||
border-image-source: url("../../assets/images/catalog/swf/habbo_skin_button_pressed_9x22.png") !important;
|
|
||||||
background: transparent !important;
|
|
||||||
background-color: transparent !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nitro-swf-button.pointer-events-none,
|
|
||||||
.nitro-swf-button:disabled {
|
|
||||||
border-image-source: url("../../assets/images/catalog/swf/habbo_skin_button_disabled_9x22.png") !important;
|
|
||||||
color: #888 !important;
|
|
||||||
opacity: 1 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nitro-swf-button-success {
|
|
||||||
height: 24px;
|
|
||||||
min-height: 24px !important;
|
|
||||||
border: 6px solid transparent !important;
|
|
||||||
border-image-source: url("../../assets/images/catalog/swf/habbo_skin_button_green_24x24.png") !important;
|
|
||||||
border-image-slice: 6 6 6 6 fill !important;
|
|
||||||
border-image-width: 6px !important;
|
|
||||||
color: #fff !important;
|
|
||||||
text-shadow: 0 1px 0 rgba(0,0,0,.55) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nitro-swf-button-success:hover {
|
|
||||||
border-image-source: url("../../assets/images/catalog/swf/habbo_skin_button_green_hover_24x24.png") !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nitro-swf-button-success:active,
|
|
||||||
.nitro-swf-button-success.active {
|
|
||||||
border-image-source: url("../../assets/images/catalog/swf/habbo_skin_button_green_pressed_24x24.png") !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nitro-swf-button-success.pointer-events-none,
|
|
||||||
.nitro-swf-button-success:disabled {
|
|
||||||
border-image-source: url("../../assets/images/catalog/swf/habbo_skin_button_green_disabled_24x24.png") !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-sm {
|
.btn-sm {
|
||||||
min-height: 28px;
|
min-height: 28px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
.habbo-swf-window {
|
.habbo-swf-window {
|
||||||
--habbo-swf-ubuntu: url("../../assets/images/catalog/swf/habbo_skin_ubuntu.png");
|
|
||||||
--habbo-swf-blue: url("../../assets/images/catalog/swf/skins/habbo_skin_blue.png");
|
|
||||||
--habbo-swf-bg: #ecece4;
|
--habbo-swf-bg: #ecece4;
|
||||||
--habbo-swf-panel: #f7f7f2;
|
--habbo-swf-panel: #f7f7f2;
|
||||||
--habbo-swf-border: #9d9d96;
|
--habbo-swf-border: #9d9d96;
|
||||||
|
|||||||
@@ -46,8 +46,6 @@ import './css/inventory/InventoryView.css';
|
|||||||
|
|
||||||
import './css/layout/LayoutTrophy.css';
|
import './css/layout/LayoutTrophy.css';
|
||||||
|
|
||||||
import './css/navigator/HabboNavigatorDesktop.css';
|
|
||||||
|
|
||||||
|
|
||||||
import './css/nitrocard/NitroCardView.css';
|
import './css/nitrocard/NitroCardView.css';
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user