mirror of
https://github.com/duckietm/Nitro-V3.git
synced 2026-06-20 07:26:19 +00:00
🆙 Init V3
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
import { AvatarScaleType, AvatarSetType, GetAvatarRenderManager } from '@nitrots/nitro-renderer';
|
||||
import { CSSProperties, FC, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { Base, BaseProps } from '../Base';
|
||||
|
||||
const AVATAR_IMAGE_CACHE: Map<string, string> = new Map();
|
||||
|
||||
export interface LayoutAvatarImageViewProps extends BaseProps<HTMLDivElement>
|
||||
{
|
||||
figure: string;
|
||||
gender?: string;
|
||||
headOnly?: boolean;
|
||||
direction?: number;
|
||||
scale?: number;
|
||||
}
|
||||
|
||||
export const LayoutAvatarImageView: FC<LayoutAvatarImageViewProps> = props =>
|
||||
{
|
||||
const { figure = '', gender = 'M', headOnly = false, direction = 0, scale = 1, classNames = [], style = {}, ...rest } = props;
|
||||
const [ avatarUrl, setAvatarUrl ] = useState<string>(null);
|
||||
const [ isReady, setIsReady ] = useState<boolean>(false);
|
||||
const isDisposed = useRef(false);
|
||||
|
||||
const getClassNames = useMemo(() =>
|
||||
{
|
||||
const newClassNames: string[] = [ 'avatar-image relative w-[90px] h-[130px] bg-no-repeat bg-[center_-8px] pointer-events-none' ];
|
||||
|
||||
if(classNames.length) newClassNames.push(...classNames);
|
||||
|
||||
return newClassNames;
|
||||
}, [ classNames ]);
|
||||
|
||||
const getStyle = useMemo(() =>
|
||||
{
|
||||
let newStyle: CSSProperties = {};
|
||||
|
||||
if(avatarUrl && avatarUrl.length) newStyle.backgroundImage = `url('${ avatarUrl }')`;
|
||||
|
||||
if(scale !== 1)
|
||||
{
|
||||
newStyle.transform = `scale(${ scale })`;
|
||||
|
||||
if(!(scale % 1)) newStyle.imageRendering = 'pixelated';
|
||||
}
|
||||
|
||||
if(Object.keys(style).length) newStyle = { ...newStyle, ...style };
|
||||
|
||||
return newStyle;
|
||||
}, [ avatarUrl, scale, style ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!isReady) return;
|
||||
|
||||
const figureKey = [ figure, gender, direction, headOnly ].join('-');
|
||||
|
||||
if(AVATAR_IMAGE_CACHE.has(figureKey))
|
||||
{
|
||||
setAvatarUrl(AVATAR_IMAGE_CACHE.get(figureKey));
|
||||
}
|
||||
else
|
||||
{
|
||||
const resetFigure = (_figure: string) =>
|
||||
{
|
||||
if(isDisposed.current) return;
|
||||
|
||||
const avatarImage = GetAvatarRenderManager().createAvatarImage(_figure, AvatarScaleType.LARGE, gender, { resetFigure: (figure: string) => resetFigure(figure), dispose: null, disposed: false });
|
||||
|
||||
let setType = AvatarSetType.FULL;
|
||||
|
||||
if(headOnly) setType = AvatarSetType.HEAD;
|
||||
|
||||
avatarImage.setDirection(setType, direction);
|
||||
|
||||
const imageUrl = avatarImage.processAsImageUrl(setType);
|
||||
|
||||
if(imageUrl && !isDisposed.current)
|
||||
{
|
||||
if(!avatarImage.isPlaceholder()) AVATAR_IMAGE_CACHE.set(figureKey, imageUrl);
|
||||
|
||||
setAvatarUrl(imageUrl);
|
||||
}
|
||||
|
||||
avatarImage.dispose();
|
||||
};
|
||||
|
||||
resetFigure(figure);
|
||||
}
|
||||
}, [ figure, gender, direction, headOnly, isReady ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
isDisposed.current = false;
|
||||
|
||||
setIsReady(true);
|
||||
|
||||
return () =>
|
||||
{
|
||||
isDisposed.current = true;
|
||||
};
|
||||
}, []);
|
||||
|
||||
return <Base classNames={ getClassNames } style={ getStyle } { ...rest } />;
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
import { FC, useMemo } from 'react';
|
||||
import { Base, BaseProps } from '../Base';
|
||||
|
||||
export interface LayoutBackgroundImageProps extends BaseProps<HTMLDivElement>
|
||||
{
|
||||
imageUrl?: string;
|
||||
}
|
||||
|
||||
export const LayoutBackgroundImage: FC<LayoutBackgroundImageProps> = props =>
|
||||
{
|
||||
const { imageUrl = null, fit = true, style = null, ...rest } = props;
|
||||
|
||||
const getStyle = useMemo(() =>
|
||||
{
|
||||
const newStyle = { ...style };
|
||||
|
||||
if(imageUrl) newStyle.background = `url(${ imageUrl }) center no-repeat`;
|
||||
|
||||
return newStyle;
|
||||
}, [ style, imageUrl ]);
|
||||
|
||||
return <Base fit={ fit } style={ getStyle } { ...rest } />;
|
||||
};
|
||||
@@ -0,0 +1,109 @@
|
||||
import { BadgeImageReadyEvent, GetEventDispatcher, GetSessionDataManager, NitroSprite, TextureUtils } from '@nitrots/nitro-renderer';
|
||||
import { CSSProperties, FC, useEffect, useMemo, useState } from 'react';
|
||||
import { GetConfigurationValue, LocalizeBadgeDescription, LocalizeBadgeName, LocalizeText } from '../../api';
|
||||
import { Base, BaseProps } from '../Base';
|
||||
|
||||
export interface LayoutBadgeImageViewProps extends BaseProps<HTMLDivElement>
|
||||
{
|
||||
badgeCode: string;
|
||||
isGroup?: boolean;
|
||||
showInfo?: boolean;
|
||||
customTitle?: string;
|
||||
isGrayscale?: boolean;
|
||||
scale?: number;
|
||||
}
|
||||
|
||||
export const LayoutBadgeImageView: FC<LayoutBadgeImageViewProps> = props =>
|
||||
{
|
||||
const { badgeCode = null, isGroup = false, showInfo = false, customTitle = null, isGrayscale = false, scale = 1, classNames = [], style = {}, children = null, ...rest } = props;
|
||||
const [ imageElement, setImageElement ] = useState<HTMLImageElement>(null);
|
||||
|
||||
const getClassNames = useMemo(() =>
|
||||
{
|
||||
const newClassNames: string[] = [ 'relative w-[40px] h-[40px] bg-no-repeat bg-center' ];
|
||||
|
||||
if(isGroup) newClassNames.push('group-badge');
|
||||
|
||||
if(isGrayscale) newClassNames.push('grayscale');
|
||||
|
||||
if(classNames.length) newClassNames.push(...classNames);
|
||||
|
||||
return newClassNames;
|
||||
}, [ classNames, isGroup, isGrayscale ]);
|
||||
|
||||
const getStyle = useMemo(() =>
|
||||
{
|
||||
let newStyle: CSSProperties = {};
|
||||
|
||||
if(imageElement)
|
||||
{
|
||||
newStyle.backgroundImage = `url(${ (isGroup) ? imageElement.src : GetConfigurationValue<string>('badge.asset.url').replace('%badgename%', badgeCode.toString()) })`;
|
||||
newStyle.width = imageElement.width;
|
||||
newStyle.height = imageElement.height;
|
||||
|
||||
if(scale !== 1)
|
||||
{
|
||||
newStyle.transform = `scale(${ scale })`;
|
||||
|
||||
if(!(scale % 1)) newStyle.imageRendering = 'pixelated';
|
||||
|
||||
newStyle.width = (imageElement.width * scale);
|
||||
newStyle.height = (imageElement.height * scale);
|
||||
}
|
||||
}
|
||||
|
||||
if(Object.keys(style).length) newStyle = { ...newStyle, ...style };
|
||||
|
||||
return newStyle;
|
||||
}, [ badgeCode, isGroup, imageElement, scale, style ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!badgeCode || !badgeCode.length) return;
|
||||
|
||||
let didSetBadge = false;
|
||||
|
||||
const onBadgeImageReadyEvent = async (event: BadgeImageReadyEvent) =>
|
||||
{
|
||||
if(event.badgeId !== badgeCode) return;
|
||||
|
||||
const element = await TextureUtils.generateImage(new NitroSprite(event.image));
|
||||
|
||||
console.log ('boe');
|
||||
|
||||
element.onload = () => setImageElement(element);
|
||||
|
||||
didSetBadge = true;
|
||||
|
||||
GetEventDispatcher().removeEventListener(BadgeImageReadyEvent.IMAGE_READY, onBadgeImageReadyEvent);
|
||||
};
|
||||
|
||||
GetEventDispatcher().addEventListener(BadgeImageReadyEvent.IMAGE_READY, onBadgeImageReadyEvent);
|
||||
|
||||
const texture = isGroup ? GetSessionDataManager().getGroupBadgeImage(badgeCode) : GetSessionDataManager().getBadgeImage(badgeCode);
|
||||
|
||||
if(texture && !didSetBadge)
|
||||
{
|
||||
(async () =>
|
||||
{
|
||||
const element = await TextureUtils.generateImage(new NitroSprite(texture));
|
||||
|
||||
|
||||
element.onload = () => setImageElement(element);
|
||||
})();
|
||||
}
|
||||
|
||||
return () => GetEventDispatcher().removeEventListener(BadgeImageReadyEvent.IMAGE_READY, onBadgeImageReadyEvent);
|
||||
}, [ badgeCode, isGroup ]);
|
||||
|
||||
return (
|
||||
<Base className="group" classNames={ getClassNames } style={ getStyle } { ...rest }>
|
||||
{ (showInfo && GetConfigurationValue<boolean>('badge.descriptions.enabled', true)) &&
|
||||
<Base className="hidden group-hover:block before:absolute before:content-['_'] before:w-[0] before:h-[0] before:!border-l-[10px] before:!border-b-[10px] before:!border-t-[10px] before:top-[10px] before:-right-[10px] before:[border-left-color:white] before:[border-top-color:transparent] before:[border-bottom-color:transparent] z-50 absolute pointer-events-none select-none w-[210px] rounded-[.25rem] bg-[#fff] -left-[220px] text-black py-1 px-2 small">
|
||||
<div className="font-bold mb-1">{ isGroup ? customTitle : LocalizeBadgeName(badgeCode) }</div>
|
||||
<div>{ isGroup ? LocalizeText('group.badgepopup.body') : LocalizeBadgeDescription(badgeCode) }</div>
|
||||
</Base> }
|
||||
{ children }
|
||||
</Base>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,42 @@
|
||||
import { FC, useMemo } from 'react';
|
||||
import { LocalizeText } from '../../api';
|
||||
import { Base, BaseProps } from '../Base';
|
||||
|
||||
interface LayoutCounterTimeViewProps extends BaseProps<HTMLDivElement>
|
||||
{
|
||||
day: string;
|
||||
hour: string;
|
||||
minutes: string;
|
||||
seconds: string;
|
||||
}
|
||||
|
||||
export const LayoutCounterTimeView: FC<LayoutCounterTimeViewProps> = props =>
|
||||
{
|
||||
const { day = '00', hour = '00', minutes = '00', seconds = '00', classNames = [], children = null, ...rest } = props;
|
||||
|
||||
const getClassNames = useMemo(() =>
|
||||
{
|
||||
const newClassNames: string[] = [ 'nitro-counter-time' ];
|
||||
|
||||
if(classNames.length) newClassNames.push(...classNames);
|
||||
|
||||
return newClassNames;
|
||||
}, [ classNames ]);
|
||||
|
||||
return (
|
||||
<div className="flex gap-1 top-2 end-2">
|
||||
<Base classNames={ getClassNames } { ...rest }>
|
||||
<div>{ day != '00' ? day : hour }{ day != '00' ? LocalizeText('countdown_clock_unit_days') : LocalizeText('countdown_clock_unit_hours') }</div>
|
||||
</Base>
|
||||
<div style={ { marginTop: '3px' } }>:</div>
|
||||
<Base className="nitro-counter-time" { ...rest }>
|
||||
<div>{ minutes }{ LocalizeText('countdown_clock_unit_minutes') }</div>
|
||||
</Base>
|
||||
<Base style={ { marginTop: '3px' } }>:</Base>
|
||||
<Base className="nitro-counter-time" { ...rest }>
|
||||
<div>{ seconds }{ LocalizeText('countdown_clock_unit_seconds') }</div>
|
||||
</Base>
|
||||
{ children }
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,44 @@
|
||||
import { CSSProperties, FC, useMemo } from 'react';
|
||||
import { GetConfigurationValue } from '../../api';
|
||||
import { Base, BaseProps } from '../Base';
|
||||
|
||||
export interface CurrencyIconProps extends BaseProps<HTMLDivElement>
|
||||
{
|
||||
type: number | string;
|
||||
}
|
||||
|
||||
export const LayoutCurrencyIcon: FC<CurrencyIconProps> = props =>
|
||||
{
|
||||
const { type = '', classNames = [], style = {}, ...rest } = props;
|
||||
|
||||
const getClassNames = useMemo(() =>
|
||||
{
|
||||
const newClassNames: string[] = [ 'nitro-currency-icon', 'bg-center bg-no-repeat w-[15px] h-[15px]' ];
|
||||
|
||||
if(classNames.length) newClassNames.push(...classNames);
|
||||
|
||||
return newClassNames;
|
||||
}, [ classNames ]);
|
||||
|
||||
const urlString = useMemo(() =>
|
||||
{
|
||||
let url = GetConfigurationValue<string>('currency.asset.icon.url', '');
|
||||
|
||||
url = url.replace('%type%', type.toString());
|
||||
|
||||
return `url(${ url })`;
|
||||
}, [ type ]);
|
||||
|
||||
const getStyle = useMemo(() =>
|
||||
{
|
||||
let newStyle: CSSProperties = {};
|
||||
|
||||
newStyle.backgroundImage = urlString;
|
||||
|
||||
if(Object.keys(style).length) newStyle = { ...newStyle, ...style };
|
||||
|
||||
return newStyle;
|
||||
}, [ style, urlString ]);
|
||||
|
||||
return <Base classNames={ getClassNames } style={ getStyle } { ...rest } />;
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
import { FC } from 'react';
|
||||
import { GetImageIconUrlForProduct } from '../../api';
|
||||
import { LayoutImage, LayoutImageProps } from './LayoutImage';
|
||||
|
||||
interface LayoutFurniIconImageViewProps extends LayoutImageProps
|
||||
{
|
||||
productType: string;
|
||||
productClassId: number;
|
||||
extraData?: string;
|
||||
}
|
||||
|
||||
export const LayoutFurniIconImageView: FC<LayoutFurniIconImageViewProps> = props =>
|
||||
{
|
||||
const { productType = 's', productClassId = -1, extraData = '', ...rest } = props;
|
||||
|
||||
return <LayoutImage className="furni-image" imageUrl={ GetImageIconUrlForProduct(productType, productClassId, extraData) } { ...rest } />;
|
||||
};
|
||||
@@ -0,0 +1,70 @@
|
||||
import { GetRoomEngine, IGetImageListener, ImageResult, TextureUtils, Vector3d } from '@nitrots/nitro-renderer';
|
||||
import { CSSProperties, FC, useEffect, useMemo, useState } from 'react';
|
||||
import { ProductTypeEnum } from '../../api';
|
||||
import { Base, BaseProps } from '../Base';
|
||||
|
||||
interface LayoutFurniImageViewProps extends BaseProps<HTMLDivElement>
|
||||
{
|
||||
productType: string;
|
||||
productClassId: number;
|
||||
direction?: number;
|
||||
extraData?: string;
|
||||
scale?: number;
|
||||
}
|
||||
|
||||
export const LayoutFurniImageView: FC<LayoutFurniImageViewProps> = props =>
|
||||
{
|
||||
const { productType = 's', productClassId = -1, direction = 2, extraData = '', scale = 1, style = {}, ...rest } = props;
|
||||
const [ imageElement, setImageElement ] = useState<HTMLImageElement>(null);
|
||||
|
||||
const getStyle = useMemo(() =>
|
||||
{
|
||||
let newStyle: CSSProperties = {};
|
||||
|
||||
if(imageElement?.src?.length)
|
||||
{
|
||||
newStyle.backgroundImage = `url('${ imageElement.src }')`;
|
||||
newStyle.width = imageElement.width;
|
||||
newStyle.height = imageElement.height;
|
||||
}
|
||||
|
||||
if(scale !== 1)
|
||||
{
|
||||
newStyle.transform = `scale(${ scale })`;
|
||||
|
||||
if(!(scale % 1)) newStyle.imageRendering = 'pixelated';
|
||||
}
|
||||
|
||||
if(Object.keys(style).length) newStyle = { ...newStyle, ...style };
|
||||
|
||||
return newStyle;
|
||||
}, [ imageElement, scale, style ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
let imageResult: ImageResult = null;
|
||||
|
||||
const listener: IGetImageListener = {
|
||||
imageReady: async (id, texture, image) => setImageElement(await TextureUtils.generateImage(texture)),
|
||||
imageFailed: null
|
||||
};
|
||||
|
||||
switch(productType.toLocaleLowerCase())
|
||||
{
|
||||
case ProductTypeEnum.FLOOR:
|
||||
imageResult = GetRoomEngine().getFurnitureFloorImage(productClassId, new Vector3d(direction), 64, listener, 0, extraData);
|
||||
break;
|
||||
case ProductTypeEnum.WALL:
|
||||
imageResult = GetRoomEngine().getFurnitureWallImage(productClassId, new Vector3d(direction), 64, listener, 0, extraData);
|
||||
break;
|
||||
}
|
||||
|
||||
if(!imageResult) return;
|
||||
|
||||
(async () => setImageElement(await TextureUtils.generateImage(imageResult.data)))();
|
||||
}, [ productType, productClassId, direction, extraData ]);
|
||||
|
||||
if(!imageElement) return null;
|
||||
|
||||
return <Base classNames={ [ 'furni-image' ] } style={ getStyle } { ...rest } />;
|
||||
};
|
||||
@@ -0,0 +1,41 @@
|
||||
import { FC } from 'react';
|
||||
import { LocalizeText } from '../../api';
|
||||
import { Column } from '../Column';
|
||||
import { Flex } from '../Flex';
|
||||
import { Text } from '../Text';
|
||||
import { LayoutAvatarImageView } from './LayoutAvatarImageView';
|
||||
|
||||
interface LayoutGiftTagViewProps
|
||||
{
|
||||
figure?: string;
|
||||
userName?: string;
|
||||
message?: string;
|
||||
editable?: boolean;
|
||||
onChange?: (value: string) => void;
|
||||
}
|
||||
|
||||
export const LayoutGiftTagView: FC<LayoutGiftTagViewProps> = props =>
|
||||
{
|
||||
const { figure = null, userName = null, message = null, editable = false, onChange = null } = props;
|
||||
|
||||
return (
|
||||
<Flex className="nitro-gift-card text-black" overflow="hidden">
|
||||
<div className="flex items-center justify-center gift-face flex-shrink-0">
|
||||
{ !userName && <div className="gift-incognito"></div> }
|
||||
{ figure && <div className="gift-avatar">
|
||||
<LayoutAvatarImageView direction={ 2 } figure={ figure } headOnly={ true } />
|
||||
</div> }
|
||||
</div>
|
||||
<Flex className="w-full pt-4 pb-4 pe-4 ps-3" overflow="hidden">
|
||||
<Column className="!flex-grow" justifyContent="between" overflow="auto">
|
||||
{ !editable &&
|
||||
<Text textBreak className="gift-message">{ message }</Text> }
|
||||
{ editable && (onChange !== null) &&
|
||||
<textarea className="gift-message h-full" maxLength={ 140 } placeholder={ LocalizeText('catalog.gift_wrapping_new.message_hint') } value={ message } onChange={ (e) => onChange(e.target.value) }></textarea> }
|
||||
{ userName &&
|
||||
<Text italics textEnd className="pe-1">{ LocalizeText('catalog.gift_wrapping_new.message_from', [ 'name' ], [ userName ]) }</Text> }
|
||||
</Column>
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,76 @@
|
||||
import { FC, useMemo } from 'react';
|
||||
import { Base } from '../Base';
|
||||
import { Column, ColumnProps } from '../Column';
|
||||
import { LayoutItemCountView } from './LayoutItemCountView';
|
||||
import { LayoutLimitedEditionStyledNumberView } from './limited-edition';
|
||||
|
||||
export interface LayoutGridItemProps extends ColumnProps
|
||||
{
|
||||
itemImage?: string;
|
||||
itemColor?: string;
|
||||
itemActive?: boolean;
|
||||
itemCount?: number;
|
||||
itemCountMinimum?: number;
|
||||
itemUniqueSoldout?: boolean;
|
||||
itemUniqueNumber?: number;
|
||||
itemUnseen?: boolean;
|
||||
itemHighlight?: boolean;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export const LayoutGridItem: FC<LayoutGridItemProps> = props =>
|
||||
{
|
||||
const { itemImage = undefined, itemColor = undefined, itemActive = false, itemCount = 1, itemCountMinimum = 1, itemUniqueSoldout = false, itemUniqueNumber = -2, itemUnseen = false, itemHighlight = false, disabled = false, center = true, column = true, style = {}, classNames = [], position = 'relative', overflow = 'hidden', children = null, ...rest } = props;
|
||||
|
||||
const getClassNames = useMemo(() =>
|
||||
{
|
||||
const newClassNames: string[] = [ 'layout-grid-item', 'border', 'border-2', 'border-muted', 'rounded' ];
|
||||
|
||||
|
||||
if(itemActive) newClassNames.push('!bg-[#ececec] !border-[#fff]');
|
||||
|
||||
if(itemUniqueSoldout || (itemUniqueNumber > 0)) newClassNames.push('unique-item');
|
||||
|
||||
if(itemUniqueSoldout) newClassNames.push('sold-out');
|
||||
|
||||
if(itemUnseen) newClassNames.push('unseen');
|
||||
|
||||
if(itemHighlight) newClassNames.push('has-highlight');
|
||||
|
||||
if(disabled) newClassNames.push('disabled');
|
||||
|
||||
if(itemImage === null) newClassNames.push('icon', 'loading-icon');
|
||||
|
||||
if(classNames.length) newClassNames.push(...classNames);
|
||||
|
||||
return newClassNames;
|
||||
}, [ itemActive, itemUniqueSoldout, itemUniqueNumber, itemUnseen, itemHighlight, disabled, itemImage, classNames ]);
|
||||
|
||||
const getStyle = useMemo(() =>
|
||||
{
|
||||
let newStyle = { ...style };
|
||||
|
||||
if(itemImage && !(itemUniqueSoldout || (itemUniqueNumber > 0))) newStyle.backgroundImage = `url(${ itemImage })`;
|
||||
|
||||
if(itemColor) newStyle.backgroundColor = itemColor;
|
||||
|
||||
if(Object.keys(style).length) newStyle = { ...newStyle, ...style };
|
||||
|
||||
return newStyle;
|
||||
}, [ style, itemImage, itemColor, itemUniqueSoldout, itemUniqueNumber ]);
|
||||
|
||||
return (
|
||||
<Column pointer center={ center } classNames={ getClassNames } column={ column } overflow={ overflow } position={ position } style={ getStyle } { ...rest }>
|
||||
{ (itemCount > itemCountMinimum) &&
|
||||
<LayoutItemCountView count={ itemCount } /> }
|
||||
{ (itemUniqueNumber > 0) &&
|
||||
<>
|
||||
<Base fit className="unique-bg-override" style={ { backgroundImage: `url(${ itemImage })` } } />
|
||||
<div className="absolute bottom-0 unique-item-counter">
|
||||
<LayoutLimitedEditionStyledNumberView value={ itemUniqueNumber } />
|
||||
</div>
|
||||
</> }
|
||||
{ children }
|
||||
</Column>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,13 @@
|
||||
import { DetailedHTMLProps, FC, HTMLAttributes } from 'react';
|
||||
|
||||
export interface LayoutImageProps extends DetailedHTMLProps<HTMLAttributes<HTMLImageElement>, HTMLImageElement>
|
||||
{
|
||||
imageUrl?: string;
|
||||
}
|
||||
|
||||
export const LayoutImage: FC<LayoutImageProps> = props =>
|
||||
{
|
||||
const { imageUrl = null, className = '', ...rest } = props;
|
||||
|
||||
return <img alt="" className={ 'no-select ' + className } src={ imageUrl } { ...rest } />;
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
import { FC, useMemo } from 'react';
|
||||
import { Base, BaseProps } from '../Base';
|
||||
|
||||
interface LayoutItemCountViewProps extends BaseProps<HTMLDivElement>
|
||||
{
|
||||
count: number;
|
||||
}
|
||||
|
||||
export const LayoutItemCountView: FC<LayoutItemCountViewProps> = props =>
|
||||
{
|
||||
const { count = 0, position = 'absolute', classNames = [], children = null, ...rest } = props;
|
||||
|
||||
const getClassNames = useMemo(() =>
|
||||
{
|
||||
const newClassNames: string[] = [ 'inline-block px-[.65em] py-[.35em] text-[.75em] font-bold leading-none text-[#fff] text-center whitespace-nowrap align-baseline rounded-[.25rem]', '!border-[1px] !border-[solid] !border-[#283F5D]', 'border-black', 'bg-danger', 'px-1', 'top-[2px] right-[2px] text-[9.5px] px-[3px] py-[2px] ' ];
|
||||
|
||||
if(classNames.length) newClassNames.push(...classNames);
|
||||
|
||||
return newClassNames;
|
||||
}, [ classNames ]);
|
||||
|
||||
return (
|
||||
<Base classNames={ getClassNames } position="absolute" { ...rest }>
|
||||
{ count }
|
||||
{ children }
|
||||
</Base>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
import { FC } from 'react';
|
||||
import { Base, BaseProps } from '../Base';
|
||||
|
||||
export const LayoutLoadingSpinnerView: FC<BaseProps<HTMLDivElement>> = props =>
|
||||
{
|
||||
const { ...rest } = props;
|
||||
|
||||
return (
|
||||
<Base classNames={ [ 'spinner-container' ] } { ...rest } >
|
||||
<Base className="spinner" />
|
||||
<Base className="spinner" />
|
||||
<Base className="spinner" />
|
||||
</Base>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,73 @@
|
||||
import { GetRoomEngine, NitroRectangle, NitroTexture } from '@nitrots/nitro-renderer';
|
||||
import { FC, useRef } from 'react';
|
||||
import { LocalizeText, PlaySound, SoundNames } from '../../api';
|
||||
import { DraggableWindow } from '../draggable-window';
|
||||
|
||||
interface LayoutMiniCameraViewProps {
|
||||
roomId: number;
|
||||
textureReceiver: (texture: NitroTexture) => Promise<void>;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export const LayoutMiniCameraView: FC<LayoutMiniCameraViewProps> = props => {
|
||||
const { roomId = -1, textureReceiver = null, onClose = null } = props;
|
||||
const elementRef = useRef<HTMLDivElement>();
|
||||
|
||||
const getCameraBounds = () => {
|
||||
if (!elementRef || !elementRef.current) return null;
|
||||
|
||||
const frameBounds = elementRef.current.getBoundingClientRect();
|
||||
|
||||
return new NitroRectangle(
|
||||
Math.floor(frameBounds.x),
|
||||
Math.floor(frameBounds.y),
|
||||
Math.floor(frameBounds.width),
|
||||
Math.floor(frameBounds.height)
|
||||
);
|
||||
};
|
||||
|
||||
const takePicture = () => {
|
||||
PlaySound(SoundNames.CAMERA_SHUTTER);
|
||||
textureReceiver(GetRoomEngine().createTextureFromRoom(roomId, 1, getCameraBounds()));
|
||||
};
|
||||
|
||||
return (
|
||||
<DraggableWindow handleSelector=".nitro-room-thumbnail-camera">
|
||||
<div className="nitro-room-thumbnail-camera px-2">
|
||||
<div
|
||||
style={{
|
||||
position: 'relative',
|
||||
paddingBottom: '192px', // Matches the space needed to position buttons as per the design
|
||||
}}
|
||||
>
|
||||
<div ref={elementRef} className="camera-frame" />
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
bottom: '10px',
|
||||
left: '10px',
|
||||
right: '10px',
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
}}
|
||||
>
|
||||
<button
|
||||
className="btn btn-sm btn-danger"
|
||||
style={{ width: '80px' }}
|
||||
onClick={onClose}
|
||||
>
|
||||
{LocalizeText('cancel')}
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-sm btn-success"
|
||||
style={{ width: '80px' }}
|
||||
onClick={takePicture}
|
||||
>
|
||||
{LocalizeText('navigator.thumbeditor.save')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DraggableWindow>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
import { FC, useMemo } from 'react';
|
||||
import { NotificationAlertType } from '../../api';
|
||||
import { NitroCardContentView, NitroCardHeaderView, NitroCardView, NitroCardViewProps } from '../card';
|
||||
|
||||
export interface LayoutNotificationAlertViewProps extends NitroCardViewProps
|
||||
{
|
||||
title?: string;
|
||||
type?: string;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export const LayoutNotificationAlertView: FC<LayoutNotificationAlertViewProps> = props =>
|
||||
{
|
||||
const { title = '', onClose = null, classNames = [], children = null,type = NotificationAlertType.DEFAULT, ...rest } = props;
|
||||
|
||||
const getClassNames = useMemo(() =>
|
||||
{
|
||||
const newClassNames: string[] = [ 'nitro-alert' ];
|
||||
|
||||
newClassNames.push('nitro-alert-' + type);
|
||||
|
||||
if(classNames.length) newClassNames.push(...classNames);
|
||||
|
||||
return newClassNames;
|
||||
}, [ classNames, type ]);
|
||||
|
||||
return (
|
||||
<NitroCardView classNames={ getClassNames } theme="primary-slim" { ...rest }>
|
||||
<NitroCardHeaderView headerText={ title } onCloseClick={ onClose } />
|
||||
<NitroCardContentView grow className="text-black" gap={ 0 } justifyContent="between" overflow="hidden">
|
||||
{ children }
|
||||
</NitroCardContentView>
|
||||
</NitroCardView>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,58 @@
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import { FC, useEffect, useMemo, useState } from 'react';
|
||||
import { Flex, FlexProps } from '../Flex';
|
||||
|
||||
export interface LayoutNotificationBubbleViewProps extends FlexProps
|
||||
{
|
||||
fadesOut?: boolean;
|
||||
timeoutMs?: number;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export const LayoutNotificationBubbleView: FC<LayoutNotificationBubbleViewProps> = props =>
|
||||
{
|
||||
const { fadesOut = true, timeoutMs = 8000, onClose = null, overflow = 'hidden', classNames = [], ...rest } = props;
|
||||
const [ isVisible, setIsVisible ] = useState(false);
|
||||
|
||||
const getClassNames = useMemo(() =>
|
||||
{
|
||||
const newClassNames: string[] = [ 'text-sm bg-[#1c1c20f2] px-[5px] py-[6px] [box-shadow:inset_0_5px_#22222799,_inset_0_-4px_#12121599] ', 'rounded' ];
|
||||
|
||||
if(classNames.length) newClassNames.push(...classNames);
|
||||
|
||||
return newClassNames;
|
||||
}, [ classNames ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
setIsVisible(true);
|
||||
|
||||
return () => setIsVisible(false);
|
||||
}, []);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!fadesOut) return;
|
||||
|
||||
const timeout = setTimeout(() =>
|
||||
{
|
||||
setIsVisible(false);
|
||||
|
||||
setTimeout(() => onClose(), 300);
|
||||
}, timeoutMs);
|
||||
|
||||
return () => clearTimeout(timeout);
|
||||
}, [ fadesOut, timeoutMs, onClose ]);
|
||||
|
||||
return (
|
||||
<AnimatePresence>
|
||||
{ isVisible &&
|
||||
<motion.div
|
||||
initial={ { opacity: 0 }}
|
||||
animate={ { opacity: 1 }}
|
||||
exit={ { opacity: 0 }}>
|
||||
<Flex overflow={ overflow } classNames={ getClassNames } onClick={ onClose } { ...rest } />
|
||||
</motion.div> }
|
||||
</AnimatePresence>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,123 @@
|
||||
import { GetRoomEngine, IPetCustomPart, PetFigureData, TextureUtils, Vector3d } from '@nitrots/nitro-renderer';
|
||||
import { CSSProperties, FC, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { Base, BaseProps } from '../Base';
|
||||
|
||||
interface LayoutPetImageViewProps extends BaseProps<HTMLDivElement>
|
||||
{
|
||||
figure?: string;
|
||||
typeId?: number;
|
||||
paletteId?: number;
|
||||
petColor?: number;
|
||||
customParts?: IPetCustomPart[];
|
||||
posture?: string;
|
||||
headOnly?: boolean;
|
||||
direction?: number;
|
||||
scale?: number;
|
||||
}
|
||||
|
||||
export const LayoutPetImageView: FC<LayoutPetImageViewProps> = props =>
|
||||
{
|
||||
const { figure = '', typeId = -1, paletteId = -1, petColor = 0xFFFFFF, customParts = [], posture = 'std', headOnly = false, direction = 0, scale = 1, style = {}, ...rest } = props;
|
||||
const [ petUrl, setPetUrl ] = useState<string>(null);
|
||||
const [ width, setWidth ] = useState(0);
|
||||
const [ height, setHeight ] = useState(0);
|
||||
const isDisposed = useRef(false);
|
||||
|
||||
const getStyle = useMemo(() =>
|
||||
{
|
||||
let newStyle: CSSProperties = {};
|
||||
|
||||
if(petUrl && petUrl.length) newStyle.backgroundImage = `url(${ petUrl })`;
|
||||
|
||||
if(scale !== 1)
|
||||
{
|
||||
newStyle.transform = `scale(${ scale })`;
|
||||
|
||||
if(!(scale % 1)) newStyle.imageRendering = 'pixelated';
|
||||
}
|
||||
|
||||
newStyle.width = width;
|
||||
newStyle.height = height;
|
||||
|
||||
if(Object.keys(style).length) newStyle = { ...newStyle, ...style };
|
||||
|
||||
return newStyle;
|
||||
}, [ petUrl, scale, style, width, height ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
let url = null;
|
||||
|
||||
let petTypeId = typeId;
|
||||
let petPaletteId = paletteId;
|
||||
let petColor1 = petColor;
|
||||
let petCustomParts: IPetCustomPart[] = customParts;
|
||||
let petHeadOnly = headOnly;
|
||||
|
||||
if(figure && figure.length)
|
||||
{
|
||||
const petFigureData = new PetFigureData(figure);
|
||||
|
||||
petTypeId = petFigureData.typeId;
|
||||
petPaletteId = petFigureData.paletteId;
|
||||
petColor1 = petFigureData.color;
|
||||
petCustomParts = petFigureData.customParts;
|
||||
}
|
||||
|
||||
if(petTypeId === 16) petHeadOnly = false;
|
||||
|
||||
const imageResult = GetRoomEngine().getRoomObjectPetImage(petTypeId, petPaletteId, petColor1, new Vector3d((direction * 45)), 64, {
|
||||
imageReady: async (id, texture, image) =>
|
||||
{
|
||||
if(isDisposed.current) return;
|
||||
|
||||
if(image)
|
||||
{
|
||||
setPetUrl(image.src);
|
||||
setWidth(image.width);
|
||||
setHeight(image.height);
|
||||
}
|
||||
|
||||
else if(texture)
|
||||
{
|
||||
setPetUrl(await TextureUtils.generateImageUrl(texture));
|
||||
setWidth(texture.width);
|
||||
setHeight(texture.height);
|
||||
}
|
||||
},
|
||||
imageFailed: (id) =>
|
||||
{
|
||||
|
||||
}
|
||||
}, petHeadOnly, 0, petCustomParts, posture);
|
||||
|
||||
if(imageResult)
|
||||
{
|
||||
(async () =>
|
||||
{
|
||||
const image = await imageResult.getImage();
|
||||
|
||||
if(image)
|
||||
{
|
||||
setPetUrl(image.src);
|
||||
setWidth(image.width);
|
||||
setHeight(image.height);
|
||||
}
|
||||
})();
|
||||
}
|
||||
}, [ figure, typeId, paletteId, petColor, customParts, posture, headOnly, direction ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
isDisposed.current = false;
|
||||
|
||||
return () =>
|
||||
{
|
||||
isDisposed.current = true;
|
||||
};
|
||||
}, []);
|
||||
|
||||
const url = `url('${ petUrl }')`;
|
||||
|
||||
return <Base classNames={ [ 'pet-image' ] } style={ getStyle } { ...rest } />;
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
import { FC } from 'react';
|
||||
import { ProductTypeEnum } from '../../api';
|
||||
import { LayoutBadgeImageView } from './LayoutBadgeImageView';
|
||||
import { LayoutCurrencyIcon } from './LayoutCurrencyIcon';
|
||||
import { LayoutFurniImageView } from './LayoutFurniImageView';
|
||||
|
||||
interface LayoutPrizeProductImageViewProps
|
||||
{
|
||||
productType: string;
|
||||
classId: number;
|
||||
extraParam?: string;
|
||||
}
|
||||
|
||||
export const LayoutPrizeProductImageView: FC<LayoutPrizeProductImageViewProps> = props =>
|
||||
{
|
||||
const { productType = ProductTypeEnum.FLOOR, classId = -1, extraParam = undefined } = props;
|
||||
|
||||
switch(productType)
|
||||
{
|
||||
case ProductTypeEnum.WALL:
|
||||
case ProductTypeEnum.FLOOR:
|
||||
return <LayoutFurniImageView productClassId={ classId } productType={ productType } />;
|
||||
case ProductTypeEnum.BADGE:
|
||||
return <LayoutBadgeImageView badgeCode={ extraParam }/>;
|
||||
case ProductTypeEnum.HABBO_CLUB:
|
||||
return <LayoutCurrencyIcon type="hc" />;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
import { FC, useMemo } from 'react';
|
||||
import { Base, Column, ColumnProps, Flex } from '..';
|
||||
|
||||
interface LayoutProgressBarProps extends ColumnProps
|
||||
{
|
||||
text?: string;
|
||||
progress: number;
|
||||
maxProgress?: number;
|
||||
}
|
||||
|
||||
export const LayoutProgressBar: FC<LayoutProgressBarProps> = props =>
|
||||
{
|
||||
const { text = '', progress = 0, maxProgress = 100, position = 'relative', justifyContent = 'center', classNames = [], children = null, ...rest } = props;
|
||||
|
||||
const getClassNames = useMemo(() =>
|
||||
{
|
||||
const newClassNames: string[] = [ 'border-[1px] border-[solid] border-[#fff] p-[2px] h-[20px] rounded-[.25rem] overflow-hidden bg-[#1E7295] ', 'text-white' ];
|
||||
|
||||
if(classNames.length) newClassNames.push(...classNames);
|
||||
|
||||
return newClassNames;
|
||||
}, [ classNames ]);
|
||||
|
||||
return (
|
||||
<Column classNames={ getClassNames } justifyContent={ justifyContent } position={ position } { ...rest }>
|
||||
{ text && (text.length > 0) &&
|
||||
<Flex center fit className="[text-shadow:0px_4px_4px_rgba(0,_0,_0,_.25)] z-20" position="absolute">{ text }</Flex> }
|
||||
<Base className="h-full z-10 [transition:all_1s] rounded-[.125rem] bg-[repeating-linear-gradient(#2DABC2,_#2DABC2_50%,_#2B91A7_50%,_#2B91A7_100%)]" style={ { width: (~~((((progress - 0) * (100 - 0)) / (maxProgress - 0)) + 0) + '%') } } />
|
||||
{ children }
|
||||
</Column>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
import { FC, useMemo } from 'react';
|
||||
import { Base, BaseProps } from '../Base';
|
||||
|
||||
interface LayoutRarityLevelViewProps extends BaseProps<HTMLDivElement>
|
||||
{
|
||||
level: number;
|
||||
}
|
||||
|
||||
export const LayoutRarityLevelView: FC<LayoutRarityLevelViewProps> = props =>
|
||||
{
|
||||
const { level = 0, classNames = [], children = null, ...rest } = props;
|
||||
|
||||
const getClassNames = useMemo(() =>
|
||||
{
|
||||
const newClassNames: string[] = [ 'nitro-rarity-level' ];
|
||||
|
||||
if(classNames.length) newClassNames.push(...classNames);
|
||||
|
||||
return newClassNames;
|
||||
}, [ classNames ]);
|
||||
|
||||
return (
|
||||
<Base classNames={ getClassNames } { ...rest }>
|
||||
<div>{ level }</div>
|
||||
{ children }
|
||||
</Base>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,59 @@
|
||||
import { GetRoomEngine, TextureUtils, Vector3d } from '@nitrots/nitro-renderer';
|
||||
import { CSSProperties, FC, useEffect, useMemo, useState } from 'react';
|
||||
import { Base, BaseProps } from '../Base';
|
||||
|
||||
interface LayoutRoomObjectImageViewProps extends BaseProps<HTMLDivElement>
|
||||
{
|
||||
roomId: number;
|
||||
objectId: number;
|
||||
category: number;
|
||||
direction?: number;
|
||||
scale?: number;
|
||||
}
|
||||
|
||||
export const LayoutRoomObjectImageView: FC<LayoutRoomObjectImageViewProps> = props =>
|
||||
{
|
||||
const { roomId = -1, objectId = 1, category = -1, direction = 2, scale = 1, style = {}, ...rest } = props;
|
||||
const [ imageElement, setImageElement ] = useState<HTMLImageElement>(null);
|
||||
|
||||
const getStyle = useMemo(() =>
|
||||
{
|
||||
let newStyle: CSSProperties = {};
|
||||
|
||||
if(imageElement?.src?.length)
|
||||
{
|
||||
newStyle.backgroundImage = `url('${ imageElement.src }')`;
|
||||
newStyle.width = imageElement.width;
|
||||
newStyle.height = imageElement.height;
|
||||
}
|
||||
|
||||
if(scale !== 1)
|
||||
{
|
||||
newStyle.transform = `scale(${ scale })`;
|
||||
|
||||
if(!(scale % 1)) newStyle.imageRendering = 'pixelated';
|
||||
}
|
||||
|
||||
if(Object.keys(style).length) newStyle = { ...newStyle, ...style };
|
||||
|
||||
return newStyle;
|
||||
}, [ imageElement, scale, style ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
const imageResult = GetRoomEngine().getRoomObjectImage(roomId, objectId, category, new Vector3d(direction * 45), 64, {
|
||||
imageReady: async (id, texture, image) => setImageElement(await TextureUtils.generateImage(texture)),
|
||||
imageFailed: null
|
||||
});
|
||||
|
||||
// needs (roomObjectImage.data.width > 140) || (roomObjectImage.data.height > 200) scale 1
|
||||
|
||||
if(!imageResult) return;
|
||||
|
||||
(async () => setImageElement(await TextureUtils.generateImage(imageResult.data)))();
|
||||
}, [ roomId, objectId, category, direction, scale ]);
|
||||
|
||||
if(!imageElement) return null;
|
||||
|
||||
return <Base classNames={ [ 'furni-image' ] } style={ getStyle } { ...rest } />;
|
||||
};
|
||||
@@ -0,0 +1,89 @@
|
||||
import { GetRenderer, GetTicker, NitroTicker, RoomPreviewer, TextureUtils } from '@nitrots/nitro-renderer';
|
||||
import { FC, MouseEvent, useEffect, useRef } from 'react';
|
||||
|
||||
export const LayoutRoomPreviewerView: FC<{
|
||||
roomPreviewer: RoomPreviewer;
|
||||
height?: number;
|
||||
}> = props =>
|
||||
{
|
||||
const { roomPreviewer = null, height = 0 } = props;
|
||||
const elementRef = useRef<HTMLDivElement>();
|
||||
|
||||
const onClick = (event: MouseEvent<HTMLDivElement>) =>
|
||||
{
|
||||
if(!roomPreviewer) return;
|
||||
|
||||
if(event.shiftKey) roomPreviewer.changeRoomObjectDirection();
|
||||
else roomPreviewer.changeRoomObjectState();
|
||||
};
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!elementRef) return;
|
||||
|
||||
const width = elementRef.current.parentElement.clientWidth;
|
||||
const texture = TextureUtils.createRenderTexture(width, height);
|
||||
|
||||
const update = async (ticker: NitroTicker) =>
|
||||
{
|
||||
if(!roomPreviewer || !elementRef.current) return;
|
||||
|
||||
roomPreviewer.updatePreviewRoomView();
|
||||
|
||||
const renderingCanvas = roomPreviewer.getRenderingCanvas();
|
||||
|
||||
if(!renderingCanvas.canvasUpdated) return;
|
||||
|
||||
GetRenderer().render({
|
||||
target: texture,
|
||||
container: renderingCanvas.master,
|
||||
clear: true
|
||||
});
|
||||
|
||||
let canvas = GetRenderer().texture.generateCanvas(texture);
|
||||
const base64 = canvas.toDataURL('image/png');
|
||||
|
||||
canvas = null;
|
||||
|
||||
elementRef.current.style.backgroundImage = `url(${ base64 })`;
|
||||
};
|
||||
|
||||
GetTicker().add(update);
|
||||
|
||||
const resizeObserver = new ResizeObserver(() =>
|
||||
{
|
||||
if(!roomPreviewer || !elementRef.current) return;
|
||||
|
||||
const width = elementRef.current.parentElement.offsetWidth;
|
||||
|
||||
roomPreviewer.modifyRoomCanvas(width, height);
|
||||
|
||||
update(GetTicker());
|
||||
});
|
||||
|
||||
roomPreviewer.getRoomCanvas(width, height);
|
||||
|
||||
resizeObserver.observe(elementRef.current);
|
||||
|
||||
return () =>
|
||||
{
|
||||
GetTicker().remove(update);
|
||||
|
||||
resizeObserver.disconnect();
|
||||
|
||||
texture.destroy(true);
|
||||
};
|
||||
}, [ roomPreviewer, elementRef, height ]);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ elementRef }
|
||||
className="relative w-full rounded-md shadow-room-previewer"
|
||||
style={ {
|
||||
height,
|
||||
minHeight: height,
|
||||
maxHeight: height
|
||||
} }
|
||||
onClick={ onClick } />
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
import { FC, useMemo } from 'react';
|
||||
import { GetConfigurationValue } from '../../api';
|
||||
import { Base, BaseProps } from '../Base';
|
||||
|
||||
export interface LayoutRoomThumbnailViewProps extends BaseProps<HTMLDivElement>
|
||||
{
|
||||
roomId?: number;
|
||||
customUrl?: string;
|
||||
}
|
||||
|
||||
export const LayoutRoomThumbnailView: FC<LayoutRoomThumbnailViewProps> = props =>
|
||||
{
|
||||
const { roomId = -1, customUrl = null, shrink = true, overflow = 'hidden', classNames = [], children = null, ...rest } = props;
|
||||
|
||||
const getClassNames = useMemo(() =>
|
||||
{
|
||||
const newClassNames: string[] = [ 'relative w-[110px] h-[110px] bg-[url("@/assets/images/navigator/thumbnail_placeholder.png")] bg-no-repeat bg-center', 'rounded', '!border-[1px] !border-[solid] !border-[#283F5D]' ];
|
||||
|
||||
if(classNames.length) newClassNames.push(...classNames);
|
||||
|
||||
return newClassNames;
|
||||
}, [ classNames ]);
|
||||
|
||||
const getImageUrl = useMemo(() =>
|
||||
{
|
||||
if(customUrl && customUrl.length) return (GetConfigurationValue<string>('image.library.url') + customUrl);
|
||||
|
||||
return (GetConfigurationValue<string>('thumbnails.url').replace('%thumbnail%', roomId.toString()));
|
||||
}, [ customUrl, roomId ]);
|
||||
|
||||
return (
|
||||
<Base classNames={ getClassNames } overflow={ overflow } shrink={ shrink } { ...rest }>
|
||||
{ getImageUrl && <img alt="" src={ getImageUrl } /> }
|
||||
{ children }
|
||||
</Base>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,42 @@
|
||||
import { FC } from 'react';
|
||||
import { LocalizeText } from '../../api';
|
||||
import { Base } from '../Base';
|
||||
import { Column } from '../Column';
|
||||
import { Flex } from '../Flex';
|
||||
import { Text } from '../Text';
|
||||
import { DraggableWindow } from '../draggable-window';
|
||||
|
||||
interface LayoutTrophyViewProps
|
||||
{
|
||||
color: string;
|
||||
message: string;
|
||||
date: string;
|
||||
senderName: string;
|
||||
customTitle?: string;
|
||||
onCloseClick: () => void;
|
||||
}
|
||||
|
||||
export const LayoutTrophyView: FC<LayoutTrophyViewProps> = props =>
|
||||
{
|
||||
const { color = '', message = '', date = '', senderName = '', customTitle = null, onCloseClick = null } = props;
|
||||
|
||||
return (
|
||||
<DraggableWindow handleSelector=".drag-handler">
|
||||
<Column alignItems="center" className={ `nitro-layout-trophy trophy-${ color }` } gap={ 0 }>
|
||||
<Flex center fullWidth className="trophy-header drag-handler" position="relative">
|
||||
<Base pointer className="trophy-close" position="absolute" onClick={ onCloseClick } />
|
||||
<Text bold>{ LocalizeText('widget.furni.trophy.title') }</Text>
|
||||
</Flex>
|
||||
<Column className="trophy-content py-1" gap={ 1 }>
|
||||
{ customTitle &&
|
||||
<Text bold>{ customTitle }</Text> }
|
||||
{ message }
|
||||
</Column>
|
||||
<Flex alignItems="center" className="trophy-footer mt-1" justifyContent="between">
|
||||
<Text bold>{ date }</Text>
|
||||
<Text bold>{ senderName }</Text>
|
||||
</Flex>
|
||||
</Column>
|
||||
</DraggableWindow>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
import { FC, useMemo } from 'react';
|
||||
import { GetUserProfile } from '../../api';
|
||||
import { Base, BaseProps } from '../Base';
|
||||
|
||||
export interface UserProfileIconViewProps extends BaseProps<HTMLDivElement>
|
||||
{
|
||||
userId?: number;
|
||||
userName?: string;
|
||||
}
|
||||
|
||||
export const UserProfileIconView: FC<UserProfileIconViewProps> = props =>
|
||||
{
|
||||
const { userId = 0, userName = null, classNames = [], pointer = true, children = null, ...rest } = props;
|
||||
|
||||
const getClassNames = useMemo(() =>
|
||||
{
|
||||
const newClassNames: string[] = [ 'bg-[url("@/assets/images/friends/friends-spritesheet.png")]', 'w-[13px] h-[11px] bg-[-51px_-91px]' ];
|
||||
|
||||
if(classNames.length) newClassNames.push(...classNames);
|
||||
|
||||
return newClassNames;
|
||||
}, [ classNames ]);
|
||||
|
||||
return (
|
||||
<Base classNames={ getClassNames } pointer={ pointer } onClick={ event => GetUserProfile(userId) } { ...rest }>
|
||||
{ children }
|
||||
</Base>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
export * from './LayoutAvatarImageView';
|
||||
export * from './LayoutBackgroundImage';
|
||||
export * from './LayoutBadgeImageView';
|
||||
export * from './LayoutCounterTimeView';
|
||||
export * from './LayoutCurrencyIcon';
|
||||
export * from './LayoutFurniIconImageView';
|
||||
export * from './LayoutFurniImageView';
|
||||
export * from './LayoutGiftTagView';
|
||||
export * from './LayoutGridItem';
|
||||
export * from './LayoutImage';
|
||||
export * from './LayoutItemCountView';
|
||||
export * from './LayoutLoadingSpinnerView';
|
||||
export * from './LayoutMiniCameraView';
|
||||
export * from './LayoutNotificationAlertView';
|
||||
export * from './LayoutNotificationBubbleView';
|
||||
export * from './LayoutPetImageView';
|
||||
export * from './LayoutProgressBar';
|
||||
export * from './LayoutRarityLevelView';
|
||||
export * from './LayoutRoomObjectImageView';
|
||||
export * from './LayoutRoomPreviewerView';
|
||||
export * from './LayoutRoomThumbnailView';
|
||||
export * from './LayoutTrophyView';
|
||||
export * from './UserProfileIconView';
|
||||
export * from './limited-edition';
|
||||
@@ -0,0 +1,35 @@
|
||||
import { FC, useMemo } from 'react';
|
||||
import { Base, BaseProps } from '../../Base';
|
||||
import { LayoutLimitedEditionStyledNumberView } from './LayoutLimitedEditionStyledNumberView';
|
||||
|
||||
interface LayoutLimitedEditionCompactPlateViewProps extends BaseProps<HTMLDivElement>
|
||||
{
|
||||
uniqueNumber: number;
|
||||
uniqueSeries: number;
|
||||
}
|
||||
|
||||
export const LayoutLimitedEditionCompactPlateView: FC<LayoutLimitedEditionCompactPlateViewProps> = props =>
|
||||
{
|
||||
const { uniqueNumber = 0, uniqueSeries = 0, classNames = [], children = null, ...rest } = props;
|
||||
|
||||
const getClassNames = useMemo(() =>
|
||||
{
|
||||
const newClassNames: string[] = [ 'unique-compact-plate', 'z-index-1' ];
|
||||
|
||||
if(classNames.length) newClassNames.push(...classNames);
|
||||
|
||||
return newClassNames;
|
||||
}, [ classNames ]);
|
||||
|
||||
return (
|
||||
<Base classNames={ getClassNames } { ...rest }>
|
||||
<div>
|
||||
<LayoutLimitedEditionStyledNumberView value={ uniqueNumber } />
|
||||
</div>
|
||||
<div>
|
||||
<LayoutLimitedEditionStyledNumberView value={ uniqueSeries } />
|
||||
</div>
|
||||
{ children }
|
||||
</Base>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,40 @@
|
||||
import { FC, useMemo } from 'react';
|
||||
import { LocalizeText } from '../../../api';
|
||||
import { Base, BaseProps } from '../../Base';
|
||||
import { Column } from '../../Column';
|
||||
import { LayoutLimitedEditionStyledNumberView } from './LayoutLimitedEditionStyledNumberView';
|
||||
|
||||
interface LayoutLimitedEditionCompletePlateViewProps extends BaseProps<HTMLDivElement>
|
||||
{
|
||||
uniqueLimitedItemsLeft: number;
|
||||
uniqueLimitedSeriesSize: number;
|
||||
}
|
||||
|
||||
export const LayoutLimitedEditionCompletePlateView: FC<LayoutLimitedEditionCompletePlateViewProps> = props =>
|
||||
{
|
||||
const { uniqueLimitedItemsLeft = 0, uniqueLimitedSeriesSize = 0, classNames = [], ...rest } = props;
|
||||
|
||||
const getClassNames = useMemo(() =>
|
||||
{
|
||||
const newClassNames: string[] = [ 'unique-complete-plate' ];
|
||||
|
||||
if(classNames.length) newClassNames.push(...classNames);
|
||||
|
||||
return newClassNames;
|
||||
}, [ classNames ]);
|
||||
|
||||
return (
|
||||
<Base classNames={ getClassNames } { ...rest }>
|
||||
<Column className="plate-container" gap={ 0 }>
|
||||
<div className="flex justify-between items-center">
|
||||
{ LocalizeText('unique.items.left') }
|
||||
<div><LayoutLimitedEditionStyledNumberView value={ uniqueLimitedItemsLeft } /></div>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
{ LocalizeText('unique.items.number.sold') }
|
||||
<div><LayoutLimitedEditionStyledNumberView value={ uniqueLimitedSeriesSize } /></div>
|
||||
</div>
|
||||
</Column>
|
||||
</Base>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,18 @@
|
||||
import { FC } from 'react';
|
||||
|
||||
interface LayoutLimitedEditionStyledNumberViewProps
|
||||
{
|
||||
value: number;
|
||||
}
|
||||
|
||||
export const LayoutLimitedEditionStyledNumberView: FC<LayoutLimitedEditionStyledNumberViewProps> = props =>
|
||||
{
|
||||
const { value = 0 } = props;
|
||||
const numbers = value.toString().split('');
|
||||
|
||||
return (
|
||||
<>
|
||||
{ numbers.map((number, index) => <i key={ index } className={ 'limited-edition-number n-' + number } />) }
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from './LayoutLimitedEditionCompactPlateView';
|
||||
export * from './LayoutLimitedEditionCompletePlateView';
|
||||
export * from './LayoutLimitedEditionStyledNumberView';
|
||||
Reference in New Issue
Block a user