Start big update

This commit is contained in:
Bill 2024-04-11 20:58:34 -04:00
parent 21409b77e0
commit e4fbb47853
365 changed files with 3900 additions and 2737 deletions

6
.editorconfig Normal file
View File

@ -0,0 +1,6 @@
[*]
charset = utf-8
insert_final_newline = true
end_of_line = lf
indent_style = space
indent_size = 4

View File

@ -105,6 +105,17 @@
{ {
"prevent": true "prevent": true
} }
],
"react/jsx-sort-props": [
"error",
{
"callbacksLast": true,
"shorthandFirst": true,
"shorthandLast": false,
"ignoreCase": true,
"noSortAlphabetically": false,
"reservedFirst": true
}
] ]
} }
} }

17
.vscode/settings.json vendored
View File

@ -8,9 +8,9 @@
"editor.codeActionsOnSave": { "editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit", "source.fixAll.eslint": "explicit",
"source.fixAll.sortJSON": "never", "source.fixAll.sortJSON": "never",
"source.organizeImports": "explicit" "source.organizeImports": "always"
}, },
"editor.formatOnSave": false, "editor.formatOnSave": true,
"git.ignoreLimitWarning": true, "git.ignoreLimitWarning": true,
"files.eol": "\n", "files.eol": "\n",
"files.insertFinalNewline": true, "files.insertFinalNewline": true,
@ -27,6 +27,15 @@
} }
], ],
"javascript.format.enable": false, "javascript.format.enable": false,
"thunder-client.saveToWorkspace": false, "editor.linkedEditing": false,
"thunder-client.workspaceRelativePath": "." "tailwindCSS.includeLanguages": {
"html": "html",
"javascript": "javascript",
"css": "css"
},
"tailwindCSS.experimental.classRegex": [
],
"editor.quickSuggestions": {
"strings": true
}
} }

View File

@ -20,7 +20,7 @@
</head> </head>
<body> <body>
<noscript>You need to enable JavaScript to run this app.</noscript> <noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root" class="w-100 h-100"></div> <div id="root" class="w-full h-full"></div>
<script> <script>
window.NitroConfig = { window.NitroConfig = {
"config.urls": [ '/renderer-config.json', '/ui-config.json' ], "config.urls": [ '/renderer-config.json', '/ui-config.json' ],

View File

@ -10,7 +10,10 @@
"eslint": "eslint src --ext .ts,.tsx" "eslint": "eslint src --ext .ts,.tsx"
}, },
"dependencies": { "dependencies": {
"@headlessui/react": "^1.7.18",
"@headlessui/tailwindcss": "^0.2.0",
"@tanstack/react-virtual": "3.2.0", "@tanstack/react-virtual": "3.2.0",
"dompurify": "^3.1.0",
"react": "^18.2.0", "react": "^18.2.0",
"react-bootstrap": "^2.2.2", "react-bootstrap": "^2.2.2",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
@ -20,6 +23,7 @@
"use-between": "^1.3.5" "use-between": "^1.3.5"
}, },
"devDependencies": { "devDependencies": {
"@tailwindcss/forms": "^0.5.7",
"@types/node": "^20.11.30", "@types/node": "^20.11.30",
"@types/react": "^18.2.67", "@types/react": "^18.2.67",
"@types/react-dom": "^18.2.22", "@types/react-dom": "^18.2.22",
@ -27,12 +31,18 @@
"@typescript-eslint/eslint-plugin": "^7.3.1", "@typescript-eslint/eslint-plugin": "^7.3.1",
"@typescript-eslint/parser": "^7.3.1", "@typescript-eslint/parser": "^7.3.1",
"@vitejs/plugin-react": "^4.2.1", "@vitejs/plugin-react": "^4.2.1",
"autoprefixer": "^10.4.19",
"eslint": "^8.57.0", "eslint": "^8.57.0",
"eslint-plugin-import": "^2.29.1", "eslint-plugin-import": "^2.29.1",
"eslint-plugin-jsx-a11y": "^6.8.0", "eslint-plugin-jsx-a11y": "^6.8.0",
"eslint-plugin-react": "^7.34.1", "eslint-plugin-react": "^7.34.1",
"eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-hooks": "^4.6.0",
"postcss": "^8.4.38",
"postcss-nested": "^6.0.1",
"prettier": "^3.2.5",
"prettier-plugin-tailwindcss": "^0.5.13",
"sass": "^1.72.0", "sass": "^1.72.0",
"tailwindcss": "^3.4.3",
"typescript": "^5.4.2", "typescript": "^5.4.2",
"vite": "^5.1.6" "vite": "^5.1.6"
} }

8
postcss.config.js Normal file
View File

@ -0,0 +1,8 @@
/** @type {import("postcss-load-config").Config} */
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {}
}
}

7
prettier.config.js Normal file
View File

@ -0,0 +1,7 @@
/** @type {import("prettier").Config} */
module.exports = {
plugins: [
'prettier-plugin-tailwindcss'
]
}

View File

@ -1,9 +1,9 @@
import { GetAssetManager, GetAvatarRenderManager, GetCommunication, GetConfiguration, GetLocalizationManager, GetRoomCameraWidgetManager, GetRoomEngine, GetRoomSessionManager, GetSessionDataManager, GetSoundManager, GetStage, GetTexturePool, GetTicker, HabboWebTools, LegacyExternalInterface, LoadGameUrlEvent, NitroLogger, NitroVersion, PrepareRenderer } from '@nitrots/nitro-renderer'; import { GetAssetManager, GetAvatarRenderManager, GetCommunication, GetConfiguration, GetLocalizationManager, GetRoomCameraWidgetManager, GetRoomEngine, GetRoomSessionManager, GetSessionDataManager, GetSoundManager, GetStage, GetTexturePool, GetTicker, HabboWebTools, LegacyExternalInterface, LoadGameUrlEvent, NitroLogger, NitroVersion, PrepareRenderer } from '@nitrots/nitro-renderer';
import { FC, useEffect, useState } from 'react'; import { FC, useEffect, useState } from 'react';
import { GetUIVersion } from './api'; import { GetUIVersion } from './api';
import { Base } from './common'; import { classNames } from './common';
import { MainView } from './components/MainView';
import { LoadingView } from './components/loading/LoadingView'; import { LoadingView } from './components/loading/LoadingView';
import { MainView } from './components/main/MainView';
import { useMessageEvent } from './hooks'; import { useMessageEvent } from './hooks';
NitroVersion.UI_VERSION = GetUIVersion(); NitroVersion.UI_VERSION = GetUIVersion();
@ -34,7 +34,9 @@ export const App: FC<{}> = props =>
height, height,
autoDensity: true, autoDensity: true,
backgroundAlpha: 0, backgroundAlpha: 0,
preference: 'webgl' preference: 'webgl',
eventMode: 'none',
failIfMajorPerformanceCaveat: false
}); });
await GetConfiguration().init(); await GetConfiguration().init();
@ -56,12 +58,12 @@ export const App: FC<{}> = props =>
GetSoundManager().init(), GetSoundManager().init(),
GetSessionDataManager().init(), GetSessionDataManager().init(),
GetRoomSessionManager().init(), GetRoomSessionManager().init(),
GetRoomCameraWidgetManager().init() GetRoomCameraWidgetManager().init(),
GetCommunication().init()
] ]
); );
await GetRoomEngine().init(); await GetRoomEngine().init();
await GetCommunication().init();
// new GameMessageHandler(); // new GameMessageHandler();
@ -91,11 +93,11 @@ export const App: FC<{}> = props =>
}, []); }, []);
return ( return (
<Base fit overflow="hidden" className={ !(window.devicePixelRatio % 1) && 'image-rendering-pixelated' }> <div className={ classNames('w-full h-full overflow-hidden', !(window.devicePixelRatio % 1) && '[image-rendering:pixelated]' ) }>
{ !isReady && { !isReady &&
<LoadingView /> } <LoadingView /> }
{ isReady && <MainView /> } { isReady && <MainView /> }
<Base id="draggable-windows-container" /> <div id="draggable-windows-container" />
</Base> </div>
); );
} }

View File

@ -82,7 +82,11 @@ export class AvatarEditorThumbnailsHelper
} }
} }
if(!hasAsset) continue; if(!hasAsset)
{
console.log(`${ AvatarFigurePartType.SCALE }_${ AvatarFigurePartType.STD }_${ part.type }_${ part.id }`);
continue;
}
const x = asset.offsetX; const x = asset.offsetX;
const y = asset.offsetY; const y = asset.offsetY;

View File

@ -1,22 +0,0 @@
import { FC, useMemo } from 'react';
import { Base, BaseProps } from './Base';
export interface ButtonGroupProps extends BaseProps<HTMLDivElement>
{
}
export const ButtonGroup: FC<ButtonGroupProps> = props =>
{
const { classNames = [], ...rest } = props;
const getClassNames = useMemo(() =>
{
const newClassNames: string[] = [ 'btn-group' ];
if(classNames.length) newClassNames.push(...classNames);
return newClassNames;
}, [ classNames ]);
return <Base classNames={ getClassNames } { ...rest } />;
}

View File

@ -39,12 +39,12 @@ export const Flex: FC<FlexProps> = props =>
if(justifyContent) newClassNames.push('justify-content-' + justifyContent); if(justifyContent) newClassNames.push('justify-content-' + justifyContent);
if(!alignItems && !justifyContent && center) newClassNames.push('align-items-center', 'justify-content-center'); if(!alignItems && !justifyContent && center) newClassNames.push('items-center', 'justify-center');
if(classNames.length) newClassNames.push(...classNames); if(classNames.length) newClassNames.push(...classNames);
return newClassNames; return newClassNames;
}, [ column, reverse, gap, center, alignSelf, alignItems, justifyContent, classNames ]); }, [ column, reverse, gap, center, alignSelf, alignItems, justifyContent, classNames ]);
return <Base display={ display } classNames={ getClassNames } { ...rest } />; return <Base classNames={ getClassNames } display={ display } { ...rest } />;
} }

View File

@ -37,7 +37,7 @@ export const Grid: FC<GridProps> = props =>
if(justifyContent) newClassNames.push('justify-content-' + justifyContent); if(justifyContent) newClassNames.push('justify-content-' + justifyContent);
if(!alignItems && !justifyContent && center) newClassNames.push('align-items-center', 'justify-content-center'); if(!alignItems && !justifyContent && center) newClassNames.push('items-center', 'justify-center');
if(classNames.length) newClassNames.push(...classNames); if(classNames.length) newClassNames.push(...classNames);
@ -57,7 +57,7 @@ export const Grid: FC<GridProps> = props =>
return ( return (
<GridContextProvider value={ { isCssGrid: true } }> <GridContextProvider value={ { isCssGrid: true } }>
<Base fullHeight={ fullHeight } classNames={ getClassNames } style={ getStyle } { ...rest } /> <Base classNames={ getClassNames } fullHeight={ fullHeight } style={ getStyle } { ...rest } />
</GridContextProvider> </GridContextProvider>
); );
} }

View File

@ -1,6 +1,5 @@
import { useVirtualizer } from '@tanstack/react-virtual'; import { useVirtualizer } from '@tanstack/react-virtual';
import { FC, Fragment, ReactElement, useEffect, useRef } from 'react'; import { FC, Fragment, ReactElement, useEffect, useRef } from 'react';
import { Base } from './Base';
import { Flex } from './Flex'; import { Flex } from './Flex';
interface InfiniteGridProps<T = any> interface InfiniteGridProps<T = any>
@ -34,7 +33,7 @@ export const InfiniteGrid: FC<InfiniteGridProps> = props =>
const items = virtualizer.getVirtualItems(); const items = virtualizer.getVirtualItems();
return ( return (
<Base innerRef={ parentRef } fit position="relative" style={ { overflowY: 'auto' } }> <div ref={ parentRef } className="size-full position-relative" style={ { overflowY: 'auto' } }>
<div <div
style={ { style={ {
height: virtualizer.getTotalSize(), height: virtualizer.getTotalSize(),
@ -54,8 +53,8 @@ export const InfiniteGrid: FC<InfiniteGridProps> = props =>
{ items.map(virtualRow => ( { items.map(virtualRow => (
<div <div
key={ virtualRow.key + 'a' } key={ virtualRow.key + 'a' }
data-index={ virtualRow.index }
ref={ virtualizer.measureElement } ref={ virtualizer.measureElement }
data-index={ virtualRow.index }
style={ { style={ {
display: 'grid', display: 'grid',
gap: '0.25rem', gap: '0.25rem',
@ -79,6 +78,6 @@ export const InfiniteGrid: FC<InfiniteGridProps> = props =>
)) } )) }
</Flex> </Flex>
</div> </div>
</Base> </div>
); );
} }

View File

@ -25,7 +25,7 @@ export const InfiniteScroll: FC<InfiniteScrollProps> = props =>
const items = virtualizer.getVirtualItems(); const items = virtualizer.getVirtualItems();
return ( return (
<Base fit innerRef={ parentRef } position="relative" overflow="auto"> <Base fit innerRef={ parentRef } overflow="auto" position="relative">
<div <div
style={ { style={ {
height: virtualizer.getTotalSize(), height: virtualizer.getTotalSize(),
@ -43,8 +43,8 @@ export const InfiniteScroll: FC<InfiniteScrollProps> = props =>
{ items.map((virtualRow) => ( { items.map((virtualRow) => (
<div <div
key={ virtualRow.key } key={ virtualRow.key }
data-index={ virtualRow.index } ref={ virtualizer.measureElement }
ref={ virtualizer.measureElement }> data-index={ virtualRow.index }>
{ rowRender(rows[virtualRow.index]) } { rowRender(rows[virtualRow.index]) }
</div> </div>
)) } )) }

View File

@ -31,15 +31,15 @@ export const NitroCardHeaderView: FC<NitroCardHeaderViewProps> = props =>
} }
return ( return (
<Column center position="relative" classNames={ getClassNames } { ...rest }> <Column center classNames={ getClassNames } position="relative" { ...rest }>
<Flex fullWidth center> <Flex center fullWidth>
<span className="nitro-card-header-text">{ headerText }</span> <span className="nitro-card-header-text">{ headerText }</span>
{ isGalleryPhoto && { isGalleryPhoto &&
<Base position="absolute" className="end-4 nitro-card-header-report-camera" onClick={ onReportPhoto }> <Base className="end-4 nitro-card-header-report-camera" position="absolute" onClick={ onReportPhoto }>
<FaFlag className="fa-icon" /> <FaFlag className="fa-icon" />
</Base> </Base>
} }
<Flex center position="absolute" className="end-2 nitro-card-header-close" onMouseDownCapture={ onMouseDown } onClick={ onCloseClick }> <Flex center className="end-2 nitro-card-header-close" position="absolute" onClick={ onCloseClick } onMouseDownCapture={ onMouseDown }>
<FaTimes className="fa-icon w-12 h-12" /> <FaTimes className="fa-icon w-12 h-12" />
</Flex> </Flex>
</Flex> </Flex>

View File

@ -1,23 +0,0 @@
import { FC, useMemo } from 'react';
import { Flex, FlexProps } from '..';
interface NitroCardSubHeaderProps extends FlexProps {
variant?: string;
}
export const NitroCardSubHeaderView: FC<NitroCardSubHeaderProps> = props =>
{
const { justifyContent = 'center', classNames = [], variant = 'muted', ...rest } = props;
const getClassNames = useMemo(() =>
{
const newClassNames: string[] = [ 'container-fluid', 'p-1' ];
if(classNames.length) newClassNames.push(...classNames);
newClassNames.push('bg-' + variant);
return newClassNames;
}, [ classNames, variant ]);
return <Flex justifyContent={ justifyContent } classNames={ getClassNames } { ...rest } />;
}

View File

@ -26,8 +26,8 @@ export const NitroCardView: FC<NitroCardViewProps> = props =>
return ( return (
<NitroCardContextProvider value={ { theme } }> <NitroCardContextProvider value={ { theme } }>
<DraggableWindow uniqueKey={ uniqueKey } handleSelector={ handleSelector } windowPosition={ windowPosition } disableDrag={ disableDrag }> <DraggableWindow disableDrag={ disableDrag } handleSelector={ handleSelector } uniqueKey={ uniqueKey } windowPosition={ windowPosition }>
<Column innerRef={ elementRef } overflow={ overflow } position={ position } gap={ gap } classNames={ getClassNames } { ...rest } /> <Column classNames={ getClassNames } gap={ gap } innerRef={ elementRef } overflow={ overflow } position={ position } { ...rest } />
</DraggableWindow> </DraggableWindow>
</NitroCardContextProvider> </NitroCardContextProvider>
); );

View File

@ -70,13 +70,13 @@ export const NitroCardAccordionSetView: FC<NitroCardAccordionSetViewProps> = pro
return ( return (
<Column classNames={ getClassNames } gap={ gap } { ...rest }> <Column classNames={ getClassNames } gap={ gap } { ...rest }>
<Flex pointer justifyContent="between" className="nitro-card-accordion-set-header px-2 py-1" onClick={ onClick }> <Flex pointer className="nitro-card-accordion-set-header px-2 py-1" justifyContent="between" onClick={ onClick }>
<Text>{ headerText }</Text> <Text>{ headerText }</Text>
{ isOpen && <FaCaretUp className="fa-icon" /> } { isOpen && <FaCaretUp className="fa-icon" /> }
{ !isOpen && <FaCaretDown className="fa-icon" /> } { !isOpen && <FaCaretDown className="fa-icon" /> }
</Flex> </Flex>
{ isOpen && { isOpen &&
<Column fullHeight overflow="auto" gap={ 0 } className="nitro-card-accordion-set-content"> <Column fullHeight className="nitro-card-accordion-set-content" gap={ 0 } overflow="auto">
{ children } { children }
</Column> } </Column> }
</Column> </Column>

View File

@ -1,7 +1,6 @@
export * from './accordion';
export * from './NitroCardContentView'; export * from './NitroCardContentView';
export * from './NitroCardContext'; export * from './NitroCardContext';
export * from './NitroCardHeaderView'; export * from './NitroCardHeaderView';
export * from './NitroCardSubHeaderView';
export * from './NitroCardView'; export * from './NitroCardView';
export * from './accordion';
export * from './tabs'; export * from './tabs';

View File

@ -24,8 +24,8 @@ export const NitroCardTabsItemView: FC<NitroCardTabsItemViewProps> = props =>
}, [ isActive, classNames ]); }, [ isActive, classNames ]);
return ( return (
<Flex overflow={ overflow } pointer={ pointer } position={ position } classNames={ getClassNames } { ...rest }> <Flex classNames={ getClassNames } overflow={ overflow } pointer={ pointer } position={ position } { ...rest }>
<Flex shrink center> <Flex center shrink>
{ children } { children }
</Flex> </Flex>
{ (count > 0) && { (count > 0) &&

View File

@ -15,7 +15,7 @@ export const NitroCardTabsView: FC<FlexProps> = props =>
}, [ classNames ]); }, [ classNames ]);
return ( return (
<Flex justifyContent={ justifyContent } gap={ gap } classNames={ getClassNames } { ...rest }> <Flex classNames={ getClassNames } gap={ gap } justifyContent={ justifyContent } { ...rest }>
{ children } { children }
</Flex> </Flex>
); );

View File

@ -263,7 +263,7 @@ export const DraggableWindow: FC<DraggableWindowProps> = props =>
return ( return (
createPortal( createPortal(
<Base position="absolute" innerRef={ elementRef } className="draggable-window" onMouseDownCapture={ onMouseDown } onTouchStartCapture={ onTouchStart } style={ dragStyle }> <Base className="draggable-window" innerRef={ elementRef } position="absolute" style={ dragStyle } onMouseDownCapture={ onMouseDown } onTouchStartCapture={ onTouchStart }>
{ children } { children }
</Base>, document.getElementById('draggable-windows-container')) </Base>, document.getElementById('draggable-windows-container'))
); );

View File

@ -1,7 +1,6 @@
export * from './AutoGrid'; export * from './AutoGrid';
export * from './Base'; export * from './Base';
export * from './Button'; export * from './Button';
export * from './ButtonGroup';
export * from './Column'; export * from './Column';
export * from './Flex'; export * from './Flex';
export * from './FormGroup'; export * from './FormGroup';

View File

@ -1,7 +1,6 @@
import { FC, useMemo } from 'react'; import { FC, useMemo } from 'react';
import { LocalizeText } from '../../api'; import { LocalizeText } from '../../api';
import { Base, BaseProps } from '../Base'; import { Base, BaseProps } from '../Base';
import { Flex } from '../Flex';
interface LayoutCounterTimeViewProps extends BaseProps<HTMLDivElement> interface LayoutCounterTimeViewProps extends BaseProps<HTMLDivElement>
{ {
@ -25,19 +24,19 @@ export const LayoutCounterTimeView: FC<LayoutCounterTimeViewProps> = props =>
}, [ classNames ]); }, [ classNames ]);
return ( return (
<Flex gap={ 1 }> <div className="flex gap-1 top-2 end-2">
<Base classNames={ getClassNames } { ...rest }> <Base classNames={ getClassNames } { ...rest }>
<div>{ day != '00' ? day : hour }{ day != '00' ? LocalizeText('countdown_clock_unit_days') : LocalizeText('countdown_clock_unit_hours') }</div> <div>{ day != '00' ? day : hour }{ day != '00' ? LocalizeText('countdown_clock_unit_days') : LocalizeText('countdown_clock_unit_hours') }</div>
</Base> </Base>
<Base style={ { marginTop: '3px' } }>:</Base> <div style={ { marginTop: '3px' } }>:</div>
<Base classNames={ getClassNames } { ...rest }> <Base className="nitro-counter-time" { ...rest }>
<div>{ minutes }{ LocalizeText('countdown_clock_unit_minutes') }</div> <div>{ minutes }{ LocalizeText('countdown_clock_unit_minutes') }</div>
</Base> </Base>
<Base style={ { marginTop: '3px' } }>:</Base> <Base style={ { marginTop: '3px' } }>:</Base>
<Base classNames={ getClassNames } { ...rest }> <Base className="nitro-counter-time" { ...rest }>
<div>{ seconds }{ LocalizeText('countdown_clock_unit_seconds') }</div> <div>{ seconds }{ LocalizeText('countdown_clock_unit_seconds') }</div>
</Base> </Base>
{ children } { children }
</Flex> </div>
); );
} }

View File

@ -13,5 +13,5 @@ export const LayoutFurniIconImageView: FC<LayoutFurniIconImageViewProps> = props
{ {
const { productType = 's', productClassId = -1, extraData = '', ...rest } = props; const { productType = 's', productClassId = -1, extraData = '', ...rest } = props;
return <LayoutImage imageUrl={ GetImageIconUrlForProduct(productType, productClassId, extraData) } className="furni-image" { ...rest } />; return <LayoutImage className="furni-image" imageUrl={ GetImageIconUrlForProduct(productType, productClassId, extraData) } { ...rest } />;
} }

View File

@ -19,19 +19,19 @@ export const LayoutGiftTagView: FC<LayoutGiftTagViewProps> = props =>
const { figure = null, userName = null, message = null, editable = false, onChange = null } = props; const { figure = null, userName = null, message = null, editable = false, onChange = null } = props;
return ( return (
<Flex overflow="hidden" className="nitro-gift-card text-black"> <Flex className="nitro-gift-card text-black" overflow="hidden">
<div className="d-flex align-items-center justify-content-center gift-face flex-shrink-0"> <div className="flex items-center justify-center gift-face flex-shrink-0">
{ !userName && <div className="gift-incognito"></div> } { !userName && <div className="gift-incognito"></div> }
{ figure && <div className="gift-avatar"> { figure && <div className="gift-avatar">
<LayoutAvatarImageView figure={ figure } direction={ 2 } headOnly={ true } /> <LayoutAvatarImageView direction={ 2 } figure={ figure } headOnly={ true } />
</div> } </div> }
</div> </div>
<Flex overflow="hidden" className="w-100 pt-4 pb-4 pe-4 ps-3"> <Flex className="w-100 pt-4 pb-4 pe-4 ps-3" overflow="hidden">
<Column grow overflow="auto" justifyContent="between"> <Column className="flex-grow-1" justifyContent="between" overflow="auto">
{ !editable && { !editable &&
<Text textBreak className="gift-message">{ message }</Text> } <Text textBreak className="gift-message">{ message }</Text> }
{ editable && (onChange !== null) && { editable && (onChange !== null) &&
<textarea className="gift-message h-100" maxLength={ 140 } value={ message } onChange={ (e) => onChange(e.target.value) } placeholder={ LocalizeText('catalog.gift_wrapping_new.message_hint') }></textarea> } <textarea className="gift-message h-100" maxLength={ 140 } placeholder={ LocalizeText('catalog.gift_wrapping_new.message_hint') } value={ message } onChange={ (e) => onChange(e.target.value) }></textarea> }
{ userName && { userName &&
<Text italics textEnd className="pe-1">{ LocalizeText('catalog.gift_wrapping_new.message_from', [ 'name' ], [ userName ]) }</Text> } <Text italics textEnd className="pe-1">{ LocalizeText('catalog.gift_wrapping_new.message_from', [ 'name' ], [ userName ]) }</Text> }
</Column> </Column>

View File

@ -59,7 +59,7 @@ export const LayoutGridItem: FC<LayoutGridItemProps> = props =>
}, [ style, itemImage, itemColor, itemUniqueSoldout, itemUniqueNumber ]); }, [ style, itemImage, itemColor, itemUniqueSoldout, itemUniqueNumber ]);
return ( return (
<Column center={ center } pointer position={ position } overflow={ overflow } column={ column } classNames={ getClassNames } style={ getStyle } { ...rest }> <Column pointer center={ center } classNames={ getClassNames } column={ column } overflow={ overflow } position={ position } style={ getStyle } { ...rest }>
{ (itemCount > itemCountMinimum) && { (itemCount > itemCountMinimum) &&
<LayoutItemCountView count={ itemCount } /> } <LayoutItemCountView count={ itemCount } /> }
{ (itemUniqueNumber > 0) && { (itemUniqueNumber > 0) &&

View File

@ -9,5 +9,5 @@ export const LayoutImage: FC<LayoutImageProps> = props =>
{ {
const { imageUrl = null, className = '', ...rest } = props; const { imageUrl = null, className = '', ...rest } = props;
return <img src={ imageUrl } className={ 'no-select ' + className } alt="" { ...rest } />; return <img alt="" className={ 'no-select ' + className } src={ imageUrl } { ...rest } />;
} }

View File

@ -20,7 +20,7 @@ export const LayoutItemCountView: FC<LayoutItemCountViewProps> = props =>
}, [ classNames ]); }, [ classNames ]);
return ( return (
<Base position="absolute" classNames={ getClassNames } { ...rest }> <Base classNames={ getClassNames } position="absolute" { ...rest }>
{ count } { count }
{ children } { children }
</Base> </Base>

View File

@ -34,7 +34,7 @@ export const LayoutMiniCameraView: FC<LayoutMiniCameraViewProps> = props =>
<DraggableWindow handleSelector=".nitro-room-thumbnail-camera"> <DraggableWindow handleSelector=".nitro-room-thumbnail-camera">
<div className="nitro-room-thumbnail-camera px-2"> <div className="nitro-room-thumbnail-camera px-2">
<div ref={ elementRef } className={ 'camera-frame' } /> <div ref={ elementRef } className={ 'camera-frame' } />
<div className="d-flex align-items-end h-100 pb-2"> <div className="flex align-items-end h-100 pb-2">
<button className="btn btn-sm btn-danger w-100 mb-1 me-2" onClick={ onClose }>{ LocalizeText('cancel') }</button> <button className="btn btn-sm btn-danger w-100 mb-1 me-2" onClick={ onClose }>{ LocalizeText('cancel') }</button>
<button className="btn btn-sm btn-success w-100 mb-1" onClick={ takePicture }>{ LocalizeText('navigator.thumbeditor.save') }</button> <button className="btn btn-sm btn-success w-100 mb-1" onClick={ takePicture }>{ LocalizeText('navigator.thumbeditor.save') }</button>
</div> </div>

View File

@ -27,7 +27,7 @@ export const LayoutNotificationAlertView: FC<LayoutNotificationAlertViewProps> =
return ( return (
<NitroCardView classNames={ getClassNames } theme="primary-slim" { ...rest }> <NitroCardView classNames={ getClassNames } theme="primary-slim" { ...rest }>
<NitroCardHeaderView headerText={ title } onCloseClick={ onClose } /> <NitroCardHeaderView headerText={ title } onCloseClick={ onClose } />
<NitroCardContentView grow justifyContent="between" overflow="hidden" className="text-black" gap={ 0 }> <NitroCardContentView grow className="text-black" gap={ 0 } justifyContent="between" overflow="hidden">
{ children } { children }
</NitroCardContentView> </NitroCardContentView>
</NitroCardView> </NitroCardView>

View File

@ -45,8 +45,8 @@ export const LayoutNotificationBubbleView: FC<LayoutNotificationBubbleViewProps>
}, [ fadesOut, timeoutMs, onClose ]); }, [ fadesOut, timeoutMs, onClose ]);
return ( return (
<TransitionAnimation type={ TransitionAnimationTypes.FADE_IN } inProp={ isVisible } timeout={ 300 }> <TransitionAnimation inProp={ isVisible } timeout={ 300 } type={ TransitionAnimationTypes.FADE_IN }>
<Flex overflow={ overflow } classNames={ getClassNames } onClick={ onClose } { ...rest } /> <Flex classNames={ getClassNames } overflow={ overflow } onClick={ onClose } { ...rest } />
</TransitionAnimation> </TransitionAnimation>
); );
} }

View File

@ -19,7 +19,7 @@ export const LayoutPrizeProductImageView: FC<LayoutPrizeProductImageViewProps> =
{ {
case ProductTypeEnum.WALL: case ProductTypeEnum.WALL:
case ProductTypeEnum.FLOOR: case ProductTypeEnum.FLOOR:
return <LayoutFurniImageView productType={ productType } productClassId={ classId } /> return <LayoutFurniImageView productClassId={ classId } productType={ productType } />
case ProductTypeEnum.BADGE: case ProductTypeEnum.BADGE:
return <LayoutBadgeImageView badgeCode={ extraParam }/> return <LayoutBadgeImageView badgeCode={ extraParam }/>
case ProductTypeEnum.HABBO_CLUB: case ProductTypeEnum.HABBO_CLUB:

View File

@ -1,7 +1,5 @@
import { FC, useMemo } from 'react'; import { FC, useMemo } from 'react';
import { Base } from '../Base';
import { Column, ColumnProps } from '../Column'; import { Column, ColumnProps } from '../Column';
import { Flex } from '../Flex';
interface LayoutProgressBarProps extends ColumnProps interface LayoutProgressBarProps extends ColumnProps
{ {
@ -24,10 +22,10 @@ export const LayoutProgressBar: FC<LayoutProgressBarProps> = props =>
}, [ classNames ]); }, [ classNames ]);
return ( return (
<Column position={ position } justifyContent={ justifyContent } classNames={ getClassNames } { ...rest }> <Column classNames={ getClassNames } justifyContent={ justifyContent } position={ position } { ...rest }>
{ text && (text.length > 0) && { text && (text.length > 0) &&
<Flex fit center position="absolute" className="nitro-progress-bar-text small">{ text }</Flex> } <div className="flex items-center justify-center size-full p-absolute nitro-progress-bar-text small">{ text }</div> }
<Base className="nitro-progress-bar-inner" style={ { width: (~~((((progress - 0) * (100 - 0)) / (maxProgress - 0)) + 0) + '%') } } /> <div className="nitro-progress-bar-inner" style={ { width: (~~((((progress - 0) * (100 - 0)) / (maxProgress - 0)) + 0) + '%') } } />
{ children } { children }
</Column> </Column>
); );

View File

@ -29,7 +29,7 @@ export const LayoutRoomThumbnailView: FC<LayoutRoomThumbnailViewProps> = props =
}, [ customUrl, roomId ]); }, [ customUrl, roomId ]);
return ( return (
<Base shrink={ shrink } overflow={ overflow } classNames={ getClassNames } { ...rest }> <Base classNames={ getClassNames } overflow={ overflow } shrink={ shrink } { ...rest }>
{ getImageUrl && <img alt="" src={ getImageUrl } /> } { getImageUrl && <img alt="" src={ getImageUrl } /> }
{ children } { children }
</Base> </Base>

View File

@ -22,9 +22,9 @@ export const LayoutTrophyView: FC<LayoutTrophyViewProps> = props =>
return ( return (
<DraggableWindow handleSelector=".drag-handler"> <DraggableWindow handleSelector=".drag-handler">
<Column gap={ 0 } alignItems="center" className={ `nitro-layout-trophy trophy-${ color }` }> <Column alignItems="center" className={ `nitro-layout-trophy trophy-${ color }` } gap={ 0 }>
<Flex center fullWidth position="relative" className="trophy-header drag-handler"> <Flex center fullWidth className="trophy-header drag-handler" position="relative">
<Base position="absolute" pointer className="trophy-close" onClick={ onCloseClick } /> <Base pointer className="trophy-close" position="absolute" onClick={ onCloseClick } />
<Text bold>{ LocalizeText('widget.furni.trophy.title') }</Text> <Text bold>{ LocalizeText('widget.furni.trophy.title') }</Text>
</Flex> </Flex>
<Column className="trophy-content py-1" gap={ 1 }> <Column className="trophy-content py-1" gap={ 1 }>
@ -32,7 +32,7 @@ export const LayoutTrophyView: FC<LayoutTrophyViewProps> = props =>
<Text bold>{ customTitle }</Text> } <Text bold>{ customTitle }</Text> }
{ message } { message }
</Column> </Column>
<Flex alignItems="center" justifyContent="between" className="trophy-footer mt-1"> <Flex alignItems="center" className="trophy-footer mt-1" justifyContent="between">
<Text bold>{ date }</Text> <Text bold>{ date }</Text>
<Text bold>{ senderName }</Text> <Text bold>{ senderName }</Text>
</Flex> </Flex>

View File

@ -2,7 +2,6 @@ import { FC, useMemo } from 'react';
import { LocalizeText } from '../../../api'; import { LocalizeText } from '../../../api';
import { Base, BaseProps } from '../../Base'; import { Base, BaseProps } from '../../Base';
import { Column } from '../../Column'; import { Column } from '../../Column';
import { Flex } from '../../Flex';
import { LayoutLimitedEditionStyledNumberView } from './LayoutLimitedEditionStyledNumberView'; import { LayoutLimitedEditionStyledNumberView } from './LayoutLimitedEditionStyledNumberView';
interface LayoutLimitedEditionCompletePlateViewProps extends BaseProps<HTMLDivElement> interface LayoutLimitedEditionCompletePlateViewProps extends BaseProps<HTMLDivElement>
@ -27,14 +26,14 @@ export const LayoutLimitedEditionCompletePlateView: FC<LayoutLimitedEditionCompl
return ( return (
<Base classNames={ getClassNames } { ...rest }> <Base classNames={ getClassNames } { ...rest }>
<Column className="plate-container" gap={ 0 }> <Column className="plate-container" gap={ 0 }>
<Flex justifyContent="between" alignItems="center"> <div className="flex justify-content-between items-center">
{ LocalizeText('unique.items.left') } { LocalizeText('unique.items.left') }
<div><LayoutLimitedEditionStyledNumberView value={ uniqueLimitedItemsLeft } /></div> <div><LayoutLimitedEditionStyledNumberView value={ uniqueLimitedItemsLeft } /></div>
</Flex> </div>
<Flex justifyContent="between" alignItems="center"> <div className="flex justify-content-between items-center">
{ LocalizeText('unique.items.number.sold') } { LocalizeText('unique.items.number.sold') }
<div><LayoutLimitedEditionStyledNumberView value={ uniqueLimitedSeriesSize } /></div> <div><LayoutLimitedEditionStyledNumberView value={ uniqueLimitedSeriesSize } /></div>
</Flex> </div>
</Column> </Column>
</Base> </Base>
); );

View File

@ -1,31 +1,31 @@
import { AddLinkEventTracker, GetCommunication, HabboWebTools, ILinkEventTracker, RemoveLinkEventTracker, RoomSessionEvent } from '@nitrots/nitro-renderer'; import { AddLinkEventTracker, GetCommunication, HabboWebTools, ILinkEventTracker, RemoveLinkEventTracker, RoomSessionEvent } from '@nitrots/nitro-renderer';
import { FC, useEffect, useState } from 'react'; import { FC, useEffect, useState } from 'react';
import { Base, TransitionAnimation, TransitionAnimationTypes } from '../../common'; import { TransitionAnimation, TransitionAnimationTypes } from '../common';
import { useNitroEvent } from '../../hooks'; import { useNitroEvent } from '../hooks';
import { AchievementsView } from '../achievements/AchievementsView'; import { AchievementsView } from './achievements/AchievementsView';
import { AvatarEditorView } from '../avatar-editor'; import { AvatarEditorView } from './avatar-editor';
import { CameraWidgetView } from '../camera/CameraWidgetView'; import { CameraWidgetView } from './camera/CameraWidgetView';
import { CampaignView } from '../campaign/CampaignView'; import { CampaignView } from './campaign/CampaignView';
import { CatalogView } from '../catalog/CatalogView'; import { CatalogView } from './catalog/CatalogView';
import { ChatHistoryView } from '../chat-history/ChatHistoryView'; import { ChatHistoryView } from './chat-history/ChatHistoryView';
import { FloorplanEditorView } from '../floorplan-editor/FloorplanEditorView'; import { FloorplanEditorView } from './floorplan-editor/FloorplanEditorView';
import { FriendsView } from '../friends/FriendsView'; import { FriendsView } from './friends/FriendsView';
import { GameCenterView } from '../game-center/GameCenterView'; import { GameCenterView } from './game-center/GameCenterView';
import { GroupsView } from '../groups/GroupsView'; import { GroupsView } from './groups/GroupsView';
import { GuideToolView } from '../guide-tool/GuideToolView'; import { GuideToolView } from './guide-tool/GuideToolView';
import { HcCenterView } from '../hc-center/HcCenterView'; import { HcCenterView } from './hc-center/HcCenterView';
import { HelpView } from '../help/HelpView'; import { HelpView } from './help/HelpView';
import { HotelView } from '../hotel-view/HotelView'; import { HotelView } from './hotel-view/HotelView';
import { InventoryView } from '../inventory/InventoryView'; import { InventoryView } from './inventory/InventoryView';
import { ModToolsView } from '../mod-tools/ModToolsView'; import { ModToolsView } from './mod-tools/ModToolsView';
import { NavigatorView } from '../navigator/NavigatorView'; import { NavigatorView } from './navigator/NavigatorView';
import { NitropediaView } from '../nitropedia/NitropediaView'; import { NitropediaView } from './nitropedia/NitropediaView';
import { RightSideView } from '../right-side/RightSideView'; import { RightSideView } from './right-side/RightSideView';
import { RoomView } from '../room/RoomView'; import { RoomView } from './room/RoomView';
import { ToolbarView } from '../toolbar/ToolbarView'; import { ToolbarView } from './toolbar/ToolbarView';
import { UserProfileView } from '../user-profile/UserProfileView'; import { UserProfileView } from './user-profile/UserProfileView';
import { UserSettingsView } from '../user-settings/UserSettingsView'; import { UserSettingsView } from './user-settings/UserSettingsView';
import { WiredView } from '../wired/WiredView'; import { WiredView } from './wired/WiredView';
export const MainView: FC<{}> = props => export const MainView: FC<{}> = props =>
{ {
@ -79,8 +79,8 @@ export const MainView: FC<{}> = props =>
}, []); }, []);
return ( return (
<Base fit> <>
<TransitionAnimation type={ TransitionAnimationTypes.FADE_IN } inProp={ landingViewVisible } timeout={ 300 }> <TransitionAnimation inProp={ landingViewVisible } timeout={ 300 } type={ TransitionAnimationTypes.FADE_IN }>
<HotelView /> <HotelView />
</TransitionAnimation> </TransitionAnimation>
<ToolbarView isInRoom={ !landingViewVisible } /> <ToolbarView isInRoom={ !landingViewVisible } />
@ -106,6 +106,6 @@ export const MainView: FC<{}> = props =>
<CampaignView /> <CampaignView />
<GameCenterView /> <GameCenterView />
<FloorplanEditorView /> <FloorplanEditorView />
</Base> </>
); );
} }

View File

@ -1,7 +1,7 @@
import { AddLinkEventTracker, ILinkEventTracker, RemoveLinkEventTracker } from '@nitrots/nitro-renderer'; import { AddLinkEventTracker, ILinkEventTracker, RemoveLinkEventTracker } from '@nitrots/nitro-renderer';
import { FC, useEffect, useState } from 'react'; import { FC, useEffect, useState } from 'react';
import { AchievementUtilities, LocalizeText } from '../../api'; import { AchievementUtilities, LocalizeText } from '../../api';
import { Base, Column, LayoutImage, LayoutProgressBar, NitroCardContentView, NitroCardHeaderView, NitroCardSubHeaderView, NitroCardView, Text } from '../../common'; import { Column, LayoutImage, LayoutProgressBar, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../common';
import { useAchievements } from '../../hooks'; import { useAchievements } from '../../hooks';
import { AchievementCategoryView } from './views/AchievementCategoryView'; import { AchievementCategoryView } from './views/AchievementCategoryView';
import { AchievementsCategoryListView } from './views/category-list/AchievementsCategoryListView'; import { AchievementsCategoryListView } from './views/category-list/AchievementsCategoryListView';
@ -44,24 +44,24 @@ export const AchievementsView: FC<{}> = props =>
if(!isVisible) return null; if(!isVisible) return null;
return ( return (
<NitroCardView uniqueKey="achievements" className="nitro-achievements" theme="primary-slim"> <NitroCardView className="nitro-achievements" theme="primary-slim" uniqueKey="achievements">
<NitroCardHeaderView headerText={ LocalizeText('inventory.achievements') } onCloseClick={ event => setIsVisible(false) } /> <NitroCardHeaderView headerText={ LocalizeText('inventory.achievements') } onCloseClick={ event => setIsVisible(false) } />
{ selectedCategory && { selectedCategory &&
<NitroCardSubHeaderView position="relative" className="justify-content-center align-items-center cursor-pointer" gap={ 3 }> <div className="position-relative gap-3 justify-center items-center cursor-pointer">
<Base onClick={ event => setSelectedCategoryCode(null) } className="nitro-achievements-back-arrow" /> <div className="nitro-achievements-back-arrow" onClick={ event => setSelectedCategoryCode(null) } />
<Column grow gap={ 0 }> <Column className="flex-grow-1" gap={ 0 }>
<Text fontSize={ 4 } fontWeight="bold" className="text-small">{ LocalizeText(`quests.${ selectedCategory.code }.name`) }</Text> <Text className="text-small" fontSize={ 4 } fontWeight="bold">{ LocalizeText(`quests.${ selectedCategory.code }.name`) }</Text>
<Text>{ LocalizeText('achievements.details.categoryprogress', [ 'progress', 'limit' ], [ selectedCategory.getProgress().toString(), selectedCategory.getMaxProgress().toString() ]) }</Text> <Text>{ LocalizeText('achievements.details.categoryprogress', [ 'progress', 'limit' ], [ selectedCategory.getProgress().toString(), selectedCategory.getMaxProgress().toString() ]) }</Text>
</Column> </Column>
<LayoutImage imageUrl={ AchievementUtilities.getAchievementCategoryImageUrl(selectedCategory, null,true) } /> <LayoutImage imageUrl={ AchievementUtilities.getAchievementCategoryImageUrl(selectedCategory, null,true) } />
</NitroCardSubHeaderView> } </div> }
<NitroCardContentView gap={ 1 }> <NitroCardContentView gap={ 1 }>
{ !selectedCategory && { !selectedCategory &&
<> <>
<AchievementsCategoryListView categories={ achievementCategories } selectedCategoryCode={ selectedCategoryCode } setSelectedCategoryCode={ setSelectedCategoryCode } /> <AchievementsCategoryListView categories={ achievementCategories } selectedCategoryCode={ selectedCategoryCode } setSelectedCategoryCode={ setSelectedCategoryCode } />
<Column grow justifyContent="end" gap={ 1 }> <Column className="flex-grow-1" gap={ 1 } justifyContent="end">
<Text small center>{ LocalizeText('achievements.categories.score', [ 'score' ], [ achievementScore.toString() ]) }</Text> <Text center small>{ LocalizeText('achievements.categories.score', [ 'score' ], [ achievementScore.toString() ]) }</Text>
<LayoutProgressBar text={ LocalizeText('achievements.categories.totalprogress', [ 'progress', 'limit' ], [ getProgress.toString(), getMaxProgress.toString() ]) } progress={ getProgress } maxProgress={ getMaxProgress } /> <LayoutProgressBar maxProgress={ getMaxProgress } progress={ getProgress } text={ LocalizeText('achievements.categories.totalprogress', [ 'progress', 'limit' ], [ getProgress.toString(), getMaxProgress.toString() ]) } />
</Column> </Column>
</> } </> }
{ selectedCategory && { selectedCategory &&

View File

@ -18,24 +18,24 @@ export const AchievementDetailsView: FC<AchievementDetailsViewProps> = props =>
return ( return (
<Flex shrink className="bg-muted rounded p-2 text-black" gap={ 2 } overflow="hidden"> <Flex shrink className="bg-muted rounded p-2 text-black" gap={ 2 } overflow="hidden">
<Column center gap={ 1 }> <Column center gap={ 1 }>
<AchievementBadgeView className="nitro-achievements-badge-image" achievement={ achievement } scale={ 2 } /> <AchievementBadgeView achievement={ achievement } className="nitro-achievements-badge-image" scale={ 2 } />
<Text fontWeight="bold"> <Text fontWeight="bold">
{ LocalizeText('achievements.details.level', [ 'level', 'limit' ], [ AchievementUtilities.getAchievementLevel(achievement).toString(), achievement.levelCount.toString() ]) } { LocalizeText('achievements.details.level', [ 'level', 'limit' ], [ AchievementUtilities.getAchievementLevel(achievement).toString(), achievement.levelCount.toString() ]) }
</Text> </Text>
</Column> </Column>
<Column fullWidth justifyContent="center" overflow="hidden"> <Column fullWidth justifyContent="center" overflow="hidden">
<Column gap={ 1 }> <div className="flex flex-column gap-1">
<Text fontWeight="bold" truncate> <Text truncate fontWeight="bold">
{ LocalizeBadgeName(AchievementUtilities.getAchievementBadgeCode(achievement)) } { LocalizeBadgeName(AchievementUtilities.getAchievementBadgeCode(achievement)) }
</Text> </Text>
<Text textBreak> <Text textBreak>
{ LocalizeBadgeDescription(AchievementUtilities.getAchievementBadgeCode(achievement)) } { LocalizeBadgeDescription(AchievementUtilities.getAchievementBadgeCode(achievement)) }
</Text> </Text>
</Column> </div>
{ ((achievement.levelRewardPoints > 0) || (achievement.scoreLimit > 0)) && { ((achievement.levelRewardPoints > 0) || (achievement.scoreLimit > 0)) &&
<Column gap={ 1 }> <div className="flex flex-column gap-1">
{ (achievement.levelRewardPoints > 0) && { (achievement.levelRewardPoints > 0) &&
<Flex alignItems="center" gap={ 1 }> <div className="flex items-center gap-1">
<Text truncate className="small"> <Text truncate className="small">
{ LocalizeText('achievements.details.reward') } { LocalizeText('achievements.details.reward') }
</Text> </Text>
@ -43,10 +43,10 @@ export const AchievementDetailsView: FC<AchievementDetailsViewProps> = props =>
{ achievement.levelRewardPoints } { achievement.levelRewardPoints }
<LayoutCurrencyIcon type={ achievement.levelRewardPointType } /> <LayoutCurrencyIcon type={ achievement.levelRewardPointType } />
</Flex> </Flex>
</Flex> } </div> }
{ (achievement.scoreLimit > 0) && { (achievement.scoreLimit > 0) &&
<LayoutProgressBar text={ LocalizeText('achievements.details.progress', [ 'progress', 'limit' ], [ (achievement.currentPoints + achievement.scoreAtStartOfLevel).toString(), (achievement.scoreLimit + achievement.scoreAtStartOfLevel).toString() ]) } progress={ (achievement.currentPoints + achievement.scoreAtStartOfLevel) } maxProgress={ (achievement.scoreLimit + achievement.scoreAtStartOfLevel) } /> } <LayoutProgressBar maxProgress={ (achievement.scoreLimit + achievement.scoreAtStartOfLevel) } progress={ (achievement.currentPoints + achievement.scoreAtStartOfLevel) } text={ LocalizeText('achievements.details.progress', [ 'progress', 'limit' ], [ (achievement.currentPoints + achievement.scoreAtStartOfLevel).toString(), (achievement.scoreLimit + achievement.scoreAtStartOfLevel).toString() ]) } /> }
</Column> } </div> }
</Column> </Column>
</Flex> </Flex>
) )

View File

@ -13,7 +13,7 @@ export const AchievementListView: FC<AchievementListViewProps> = props =>
const { achievements = null } = props; const { achievements = null } = props;
return ( return (
<AutoGrid columnCount={ 6 } columnMinWidth={ 50 } columnMinHeight={ 50 }> <AutoGrid columnCount={ 6 } columnMinHeight={ 50 } columnMinWidth={ 50 }>
{ achievements && (achievements.length > 0) && achievements.map((achievement, index) => <AchievementListItemView key={ index } achievement={ achievement } />) } { achievements && (achievements.length > 0) && achievements.map((achievement, index) => <AchievementListItemView key={ index } achievement={ achievement } />) }
</AutoGrid> </AutoGrid>
); );

View File

@ -21,10 +21,10 @@ export const AchievementsCategoryListItemView: FC<AchievementCategoryListItemVie
const getTotalUnseen = AchievementUtilities.getAchievementCategoryTotalUnseen(category); const getTotalUnseen = AchievementUtilities.getAchievementCategoryTotalUnseen(category);
return ( return (
<LayoutGridItem itemActive={ (selectedCategoryCode === category.code) } itemCount={ getTotalUnseen } itemCountMinimum={ 0 } gap={ 1 } onClick={ event => setSelectedCategoryCode(category.code) }> <LayoutGridItem gap={ 1 } itemActive={ (selectedCategoryCode === category.code) } itemCount={ getTotalUnseen } itemCountMinimum={ 0 } onClick={ event => setSelectedCategoryCode(category.code) }>
<Text fullWidth center small className="pt-1">{ LocalizeText(`quests.${ category.code }.name`) }</Text> <Text center fullWidth small className="pt-1">{ LocalizeText(`quests.${ category.code }.name`) }</Text>
<LayoutBackgroundImage position="relative" imageUrl={ getCategoryImage }> <LayoutBackgroundImage imageUrl={ getCategoryImage } position="relative">
<Text fullWidth center position="absolute" variant="white" style={ { fontSize: 12, bottom: 9 } }>{ progress } / { maxProgress }</Text> <Text center fullWidth position="absolute" style={ { fontSize: 12, bottom: 9 } } variant="white">{ progress } / { maxProgress }</Text>
</LayoutBackgroundImage> </LayoutBackgroundImage>
</LayoutGridItem> </LayoutGridItem>
); );

View File

@ -15,7 +15,7 @@ export const AchievementsCategoryListView: FC<AchievementsCategoryListViewProps>
const { categories = null, selectedCategoryCode = null, setSelectedCategoryCode = null } = props; const { categories = null, selectedCategoryCode = null, setSelectedCategoryCode = null } = props;
return ( return (
<AutoGrid columnCount={ 3 } columnMinWidth={ 90 } columnMinHeight={ 100 }> <AutoGrid columnCount={ 3 } columnMinHeight={ 100 } columnMinWidth={ 90 }>
{ categories && (categories.length > 0) && categories.map((category, index) => <AchievementsCategoryListItemView key={ index } category={ category } selectedCategoryCode={ selectedCategoryCode } setSelectedCategoryCode={ setSelectedCategoryCode } /> ) } { categories && (categories.length > 0) && categories.map((category, index) => <AchievementsCategoryListItemView key={ index } category={ category } selectedCategoryCode={ selectedCategoryCode } setSelectedCategoryCode={ setSelectedCategoryCode } /> ) }
</AutoGrid> </AutoGrid>
); );

View File

@ -1,6 +1,6 @@
import { AvatarDirectionAngle } from '@nitrots/nitro-renderer'; import { AvatarDirectionAngle } from '@nitrots/nitro-renderer';
import { FC, useState } from 'react'; import { FC, useState } from 'react';
import { Base, Column, LayoutAvatarImageView } from '../../common'; import { LayoutAvatarImageView } from '../../common';
import { useAvatarEditor } from '../../hooks'; import { useAvatarEditor } from '../../hooks';
import { AvatarEditorIcon } from './AvatarEditorIcon'; import { AvatarEditorIcon } from './AvatarEditorIcon';
@ -27,14 +27,14 @@ export const AvatarEditorFigurePreviewView: FC<{}> = props =>
} }
return ( return (
<Column className="figure-preview-container" overflow="hidden" position="relative"> <div className="flex flex-column figure-preview-container overflow-hidden position-relative">
<LayoutAvatarImageView figure={ getFigureString } direction={ direction } scale={ 2 } /> <LayoutAvatarImageView direction={ direction } figure={ getFigureString } scale={ 2 } />
<AvatarEditorIcon className="avatar-spotlight" icon="spotlight" /> <AvatarEditorIcon className="avatar-spotlight" icon="spotlight" />
<Base className="avatar-shadow" /> <div className="avatar-shadow" />
<Base className="arrow-container"> <div className="arrow-container">
<AvatarEditorIcon pointer icon="arrow-left" onClick={ event => rotateFigure(direction + 1) } /> <AvatarEditorIcon icon="arrow-left" onClick={ event => rotateFigure(direction + 1) } />
<AvatarEditorIcon pointer icon="arrow-right" onClick={ event => rotateFigure(direction - 1) } /> <AvatarEditorIcon icon="arrow-right" onClick={ event => rotateFigure(direction - 1) } />
</Base> </div>
</Column> </div>
); );
} }

View File

@ -1,27 +1,27 @@
import { FC, useMemo } from 'react'; import { DetailedHTMLProps, HTMLAttributes, PropsWithChildren, forwardRef } from 'react';
import { Base, BaseProps } from '../../common'; import { classNames } from '../../common';
type AvatarIconType = 'male' | 'female' | 'clear' | 'sellable' | string; type AvatarIconType = 'male' | 'female' | 'clear' | 'sellable' | string;
export const AvatarEditorIcon: FC<{ export const AvatarEditorIcon = forwardRef<HTMLDivElement, PropsWithChildren<{
icon: AvatarIconType; icon: AvatarIconType;
selected?: boolean; selected?: boolean;
} & BaseProps<HTMLDivElement>> = props => }> & DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>>((props, ref) =>
{ {
const { icon = null, selected = false, classNames = [], children = null, ...rest } = props; const { icon = null, selected = false, className = null, ...rest } = props;
const getClassNames = useMemo(() => return (
{ <div
const newClassNames: string[] = [ 'nitro-avatar-editor-spritesheet' ]; ref={ ref }
className={ classNames(
'nitro-avatar-editor-spritesheet',
'cursor-pointer',
`${ icon }-icon`,
selected && 'selected',
className
) }
{ ...rest } />
);
});
if(icon && icon.length) newClassNames.push(icon + '-icon'); AvatarEditorIcon.displayName = 'AvatarEditorIcon';
if(selected) newClassNames.push('selected');
if(classNames.length) newClassNames.push(...classNames);
return newClassNames;
}, [ icon, selected, classNames ]);
return <Base classNames={ getClassNames } { ...rest } />
}

View File

@ -1,7 +1,6 @@
import { AvatarEditorFigureCategory, AvatarFigurePartType, FigureDataContainer } from '@nitrots/nitro-renderer'; import { AvatarEditorFigureCategory, AvatarFigurePartType, FigureDataContainer } from '@nitrots/nitro-renderer';
import { FC, useCallback, useEffect, useMemo, useState } from 'react'; import { FC, useCallback, useEffect, useMemo, useState } from 'react';
import { IAvatarEditorCategory } from '../../api'; import { IAvatarEditorCategory } from '../../api';
import { Column, Flex, Grid } from '../../common';
import { useAvatarEditor } from '../../hooks'; import { useAvatarEditor } from '../../hooks';
import { AvatarEditorIcon } from './AvatarEditorIcon'; import { AvatarEditorIcon } from './AvatarEditorIcon';
import { AvatarEditorFigureSetView } from './figure-set'; import { AvatarEditorFigureSetView } from './figure-set';
@ -47,35 +46,35 @@ export const AvatarEditorModelView: FC<{
if(!activeCategory) return null; if(!activeCategory) return null;
return ( return (
<Grid> <div className="flex gap-2 overflow-hidden">
<Column size={ 2 }> <div className="flex flex-column col-2">
{ (name === AvatarEditorFigureCategory.GENERIC) && { (name === AvatarEditorFigureCategory.GENERIC) &&
<> <>
<Flex center pointer className="category-item" onClick={ event => setGender(AvatarFigurePartType.MALE) }> <div className="category-item items-center justify-center cursor-pointer flex" onClick={ event => setGender(AvatarFigurePartType.MALE) }>
<AvatarEditorIcon icon="male" selected={ gender === FigureDataContainer.MALE } /> <AvatarEditorIcon icon="male" selected={ gender === FigureDataContainer.MALE } />
</Flex> </div>
<Flex center pointer className="category-item" onClick={ event => setGender(AvatarFigurePartType.FEMALE) }> <div className="category-item items-center justify-center cursor-pointer flex" onClick={ event => setGender(AvatarFigurePartType.FEMALE) }>
<AvatarEditorIcon icon="female" selected={ gender === FigureDataContainer.FEMALE } /> <AvatarEditorIcon icon="female" selected={ gender === FigureDataContainer.FEMALE } />
</Flex> </div>
</> } </> }
{ (name !== AvatarEditorFigureCategory.GENERIC) && (categories.length > 0) && categories.map(category => { (name !== AvatarEditorFigureCategory.GENERIC) && (categories.length > 0) && categories.map(category =>
{ {
return ( return (
<Flex center pointer key={ category.setType } className="category-item" onClick={ event => selectSet(category.setType) }> <div key={ category.setType } className="category-item items-center justify-center cursor-pointer flex" onClick={ event => selectSet(category.setType) }>
<AvatarEditorIcon icon={ category.setType } selected={ (activeSetType === category.setType) } /> <AvatarEditorIcon icon={ category.setType } selected={ (activeSetType === category.setType) } />
</Flex> </div>
); );
}) } }) }
</Column> </div>
<Column size={ 5 } overflow="hidden"> <div className="flex flex-column overflow-hidden col-5">
<AvatarEditorFigureSetView category={ activeCategory } /> <AvatarEditorFigureSetView category={ activeCategory } columnCount={ 3 } />
</Column> </div>
<Column size={ 5 } overflow="hidden"> <div className="flex flex-column overflow-hidden col-5">
{ (maxPaletteCount >= 1) && { (maxPaletteCount >= 1) &&
<AvatarEditorPaletteSetView category={ activeCategory } paletteIndex={ 0 } /> } <AvatarEditorPaletteSetView category={ activeCategory } columnCount={ 3 } paletteIndex={ 0 } /> }
{ (maxPaletteCount === 2) && { (maxPaletteCount === 2) &&
<AvatarEditorPaletteSetView category={ activeCategory } paletteIndex={ 1 } /> } <AvatarEditorPaletteSetView category={ activeCategory } columnCount={ 3 } paletteIndex={ 1 } /> }
</Column> </div>
</Grid> </div>
); );
} }

View File

@ -2,7 +2,7 @@ import { AddLinkEventTracker, AvatarEditorFigureCategory, GetSessionDataManager,
import { FC, useEffect, useState } from 'react'; import { FC, useEffect, useState } from 'react';
import { FaDice, FaRedo, FaTrash } from 'react-icons/fa'; import { FaDice, FaRedo, FaTrash } from 'react-icons/fa';
import { AvatarEditorAction, LocalizeText, SendMessageComposer } from '../../api'; import { AvatarEditorAction, LocalizeText, SendMessageComposer } from '../../api';
import { Button, ButtonGroup, Column, Grid, NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView } from '../../common'; import { Button, NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView } from '../../common';
import { useAvatarEditor } from '../../hooks'; import { useAvatarEditor } from '../../hooks';
import { AvatarEditorFigurePreviewView } from './AvatarEditorFigurePreviewView'; import { AvatarEditorFigurePreviewView } from './AvatarEditorFigurePreviewView';
import { AvatarEditorModelView } from './AvatarEditorModelView'; import { AvatarEditorModelView } from './AvatarEditorModelView';
@ -74,7 +74,7 @@ export const AvatarEditorView: FC<{}> = props =>
if(!isVisible) return null; if(!isVisible) return null;
return ( return (
<NitroCardView uniqueKey="avatar-editor" className="nitro-avatar-editor"> <NitroCardView className="nitro-avatar-editor" uniqueKey="avatar-editor">
<NitroCardHeaderView headerText={ LocalizeText('avatareditor.title') } onCloseClick={ event => setIsVisible(false) } /> <NitroCardHeaderView headerText={ LocalizeText('avatareditor.title') } onCloseClick={ event => setIsVisible(false) } />
<NitroCardTabsView> <NitroCardTabsView>
{ Object.keys(avatarModels).map(modelKey => { Object.keys(avatarModels).map(modelKey =>
@ -89,17 +89,17 @@ export const AvatarEditorView: FC<{}> = props =>
}) } }) }
</NitroCardTabsView> </NitroCardTabsView>
<NitroCardContentView> <NitroCardContentView>
<Grid> <div className="grid gap-2 overflow-hidden">
<Column size={ 9 } overflow="hidden"> <div className="flex flex-column col-9 overflow-hidden">
{ ((activeModelKey.length > 0) && (activeModelKey !== AvatarEditorFigureCategory.WARDROBE)) && { ((activeModelKey.length > 0) && (activeModelKey !== AvatarEditorFigureCategory.WARDROBE)) &&
<AvatarEditorModelView name={ activeModelKey } categories={ avatarModels[activeModelKey] } /> } <AvatarEditorModelView categories={ avatarModels[activeModelKey] } name={ activeModelKey } /> }
{ (activeModelKey === AvatarEditorFigureCategory.WARDROBE) && { (activeModelKey === AvatarEditorFigureCategory.WARDROBE) &&
<AvatarEditorWardrobeView /> } <AvatarEditorWardrobeView /> }
</Column> </div>
<Column size={ 3 } overflow="hidden"> <div className="flex flex-column col-3 overflow-hidden gap-1">
<AvatarEditorFigurePreviewView /> <AvatarEditorFigurePreviewView />
<Column grow gap={ 1 }> <div className="flex flex-column flex-grow-1 gap-1">
<ButtonGroup> <div className="btn-group">
<Button variant="secondary" onClick={ event => processAction(AvatarEditorAction.ACTION_RESET) }> <Button variant="secondary" onClick={ event => processAction(AvatarEditorAction.ACTION_RESET) }>
<FaRedo className="fa-icon" /> <FaRedo className="fa-icon" />
</Button> </Button>
@ -109,13 +109,13 @@ export const AvatarEditorView: FC<{}> = props =>
<Button variant="secondary" onClick={ event => processAction(AvatarEditorAction.ACTION_RANDOMIZE) }> <Button variant="secondary" onClick={ event => processAction(AvatarEditorAction.ACTION_RANDOMIZE) }>
<FaDice className="fa-icon" /> <FaDice className="fa-icon" />
</Button> </Button>
</ButtonGroup> </div>
<Button className="w-100" variant="success" onClick={ event => processAction(AvatarEditorAction.ACTION_SAVE) }> <Button className="w-100" variant="success" onClick={ event => processAction(AvatarEditorAction.ACTION_SAVE) }>
{ LocalizeText('avatareditor.save') } { LocalizeText('avatareditor.save') }
</Button> </Button>
</Column> </div>
</Column> </div>
</Grid> </div>
</NitroCardContentView> </NitroCardContentView>
</NitroCardView> </NitroCardView>
); );

View File

@ -1,7 +1,7 @@
import { GetAvatarRenderManager, IAvatarFigureContainer, SaveWardrobeOutfitMessageComposer } from '@nitrots/nitro-renderer'; import { GetAvatarRenderManager, IAvatarFigureContainer, SaveWardrobeOutfitMessageComposer } from '@nitrots/nitro-renderer';
import { FC, useCallback } from 'react'; import { FC, useCallback } from 'react';
import { GetClubMemberLevel, GetConfigurationValue, LocalizeText, SendMessageComposer } from '../../api'; import { GetClubMemberLevel, GetConfigurationValue, LocalizeText, SendMessageComposer } from '../../api';
import { Base, Button, Flex, InfiniteGrid, LayoutAvatarImageView, LayoutCurrencyIcon, LayoutGridItem } from '../../common'; import { Button, InfiniteGrid, LayoutAvatarImageView, LayoutCurrencyIcon, LayoutGridItem } from '../../common';
import { useAvatarEditor } from '../../hooks'; import { useAvatarEditor } from '../../hooks';
export const AvatarEditorWardrobeView: FC<{}> = props => export const AvatarEditorWardrobeView: FC<{}> = props =>
@ -34,7 +34,7 @@ export const AvatarEditorWardrobeView: FC<{}> = props =>
}, [ getFigureString, gender, savedFigures, setSavedFigures ]); }, [ getFigureString, gender, savedFigures, setSavedFigures ]);
return ( return (
<InfiniteGrid rows={ savedFigures } columnCount={ 5 } overscan={ 5 } estimateSize={ 140 } itemRender={ (item: [ IAvatarFigureContainer, string ], index: number) => <InfiniteGrid columnCount={ 5 } estimateSize={ 140 } itemRender={ (item: [ IAvatarFigureContainer, string ], index: number) =>
{ {
const [ figureContainer, gender ] = item; const [ figureContainer, gender ] = item;
@ -43,18 +43,18 @@ export const AvatarEditorWardrobeView: FC<{}> = props =>
if(figureContainer) clubLevel = GetAvatarRenderManager().getFigureClubLevel(figureContainer, gender); if(figureContainer) clubLevel = GetAvatarRenderManager().getFigureClubLevel(figureContainer, gender);
return ( return (
<LayoutGridItem position="relative" overflow="hidden" className="nitro-avatar-editor-wardrobe-figure-preview"> <LayoutGridItem className="nitro-avatar-editor-wardrobe-figure-preview" overflow="hidden" position="relative">
{ figureContainer && { figureContainer &&
<LayoutAvatarImageView figure={ figureContainer.getFigureString() } gender={ gender } direction={ 2 } /> } <LayoutAvatarImageView direction={ 2 } figure={ figureContainer.getFigureString() } gender={ gender } /> }
<Base className="avatar-shadow" /> <div className="avatar-shadow" />
{ !hcDisabled && (clubLevel > 0) && <LayoutCurrencyIcon className="position-absolute top-1 start-1" type="hc" /> } { !hcDisabled && (clubLevel > 0) && <LayoutCurrencyIcon className="position-absolute top-1 start-1" type="hc" /> }
<Flex gap={ 1 } className="button-container"> <div className="flex gap-1 button-container">
<Button variant="link" fullWidth onClick={ event => saveFigureAtWardrobeIndex(index) }>{ LocalizeText('avatareditor.wardrobe.save') }</Button> <Button fullWidth variant="link" onClick={ event => saveFigureAtWardrobeIndex(index) }>{ LocalizeText('avatareditor.wardrobe.save') }</Button>
{ figureContainer && { figureContainer &&
<Button variant="link" fullWidth onClick={ event => wearFigureAtIndex(index) } disabled={ (clubLevel > GetClubMemberLevel()) }>{ LocalizeText('widget.generic_usable.button.use') }</Button> } <Button fullWidth disabled={ (clubLevel > GetClubMemberLevel()) } variant="link" onClick={ event => wearFigureAtIndex(index) }>{ LocalizeText('widget.generic_usable.button.use') }</Button> }
</Flex> </div>
</LayoutGridItem> </LayoutGridItem>
) )
} } /> } } overscan={ 5 } rows={ savedFigures } />
); );
} }

View File

@ -9,9 +9,10 @@ export const AvatarEditorFigureSetItemView: FC<{
setType: string; setType: string;
partItem: IAvatarEditorCategoryPartItem; partItem: IAvatarEditorCategoryPartItem;
isSelected: boolean; isSelected: boolean;
width?: string;
} & LayoutGridItemProps> = props => } & LayoutGridItemProps> = props =>
{ {
const { setType = null, partItem = null, isSelected = false, ...rest } = props; const { setType = null, partItem = null, isSelected = false, width = '100%', ...rest } = props;
const [ assetUrl, setAssetUrl ] = useState<string>(''); const [ assetUrl, setAssetUrl ] = useState<string>('');
const { selectedColorParts = null, getFigureStringWithFace = null } = useAvatarEditor(); const { selectedColorParts = null, getFigureStringWithFace = null } = useAvatarEditor();
@ -45,10 +46,10 @@ export const AvatarEditorFigureSetItemView: FC<{
if(!partItem) return null; if(!partItem) return null;
return ( return (
<LayoutGridItem itemImage={ (partItem.isClear ? undefined : assetUrl) } itemActive={ isSelected } style={ { width: '100%', flex: '1', backgroundPosition: (setType === AvatarFigurePartType.HEAD) ? 'center -35px' : 'center' } } { ...rest }> <LayoutGridItem itemActive={ isSelected } itemImage={ (partItem.isClear ? undefined : assetUrl) } style={ { flex: '1', backgroundPosition: (setType === AvatarFigurePartType.HEAD) ? 'center -35px' : 'center' } } { ...rest }>
{ !partItem.isClear && isHC && <LayoutCurrencyIcon className="position-absolute end-1 bottom-1" type="hc" /> } { !partItem.isClear && isHC && <LayoutCurrencyIcon className="position-absolute end-1 bottom-1" type="hc" /> }
{ partItem.isClear && <AvatarEditorIcon icon="clear" /> } { partItem.isClear && <AvatarEditorIcon icon="clear" /> }
{ !partItem.isClear && partItem.partSet.isSellable && <AvatarEditorIcon icon="sellable" position="absolute" className="end-1 bottom-1" /> } { !partItem.isClear && partItem.partSet.isSellable && <AvatarEditorIcon className="end-1 bottom-1 position-absolute" icon="sellable" /> }
</LayoutGridItem> </LayoutGridItem>
); );
} }

View File

@ -5,10 +5,11 @@ import { useAvatarEditor } from '../../../hooks';
import { AvatarEditorFigureSetItemView } from './AvatarEditorFigureSetItemView'; import { AvatarEditorFigureSetItemView } from './AvatarEditorFigureSetItemView';
export const AvatarEditorFigureSetView: FC<{ export const AvatarEditorFigureSetView: FC<{
category: IAvatarEditorCategory category: IAvatarEditorCategory;
columnCount: number;
}> = props => }> = props =>
{ {
const { category = null } = props; const { category = null, columnCount = 3 } = props;
const { selectedParts = null, selectEditorPart } = useAvatarEditor(); const { selectedParts = null, selectEditorPart } = useAvatarEditor();
const isPartItemSelected = (partItem: IAvatarEditorCategoryPartItem) => const isPartItemSelected = (partItem: IAvatarEditorCategoryPartItem) =>
@ -28,13 +29,13 @@ export const AvatarEditorFigureSetView: FC<{
} }
return ( return (
<InfiniteGrid rows={ category.partItems } columnCount={ 3 } overscan={ 5 } itemRender={ (item: IAvatarEditorCategoryPartItem) => <InfiniteGrid columnCount={ columnCount } itemRender={ (item: IAvatarEditorCategoryPartItem) =>
{ {
if(!item) return null; if(!item) return null;
return ( return (
<AvatarEditorFigureSetItemView setType={ category.setType } partItem={ item } isSelected={ isPartItemSelected(item) } onClick={ event => selectEditorPart(category.setType, item.partSet?.id ?? -1) } /> <AvatarEditorFigureSetItemView isSelected={ isPartItemSelected(item) } partItem={ item } setType={ category.setType } width={ `calc(100% / ${ columnCount }` } onClick={ event => selectEditorPart(category.setType, item.partSet?.id ?? -1) } />
) )
} } /> } } overscan={ columnCount } rows={ category.partItems } />
); );
} }

View File

@ -7,16 +7,17 @@ export const AvatarEditorPaletteSetItem: FC<{
setType: string; setType: string;
partColor: IPartColor; partColor: IPartColor;
isSelected: boolean; isSelected: boolean;
width?: string;
} & LayoutGridItemProps> = props => } & LayoutGridItemProps> = props =>
{ {
const { setType = null, partColor = null, isSelected = false, ...rest } = props; const { setType = null, partColor = null, isSelected = false, width = '100%', ...rest } = props;
if(!partColor) return null; if(!partColor) return null;
const isHC = !GetConfigurationValue<boolean>('hc.disabled', false) && (partColor.clubLevel > 0); const isHC = !GetConfigurationValue<boolean>('hc.disabled', false) && (partColor.clubLevel > 0);
return ( return (
<LayoutGridItem itemHighlight itemColor={ ColorConverter.int2rgb(partColor.rgb) } itemActive={ isSelected } className="clear-bg" { ...rest }> <LayoutGridItem itemHighlight className="clear-bg" itemActive={ isSelected } itemColor={ ColorConverter.int2rgb(partColor.rgb) } { ...rest }>
{ isHC && <LayoutCurrencyIcon className="position-absolute end-1 bottom-1" type="hc" /> } { isHC && <LayoutCurrencyIcon className="position-absolute end-1 bottom-1" type="hc" /> }
</LayoutGridItem> </LayoutGridItem>
); );

View File

@ -6,11 +6,12 @@ import { useAvatarEditor } from '../../../hooks';
import { AvatarEditorPaletteSetItem } from './AvatarEditorPaletteSetItemView'; import { AvatarEditorPaletteSetItem } from './AvatarEditorPaletteSetItemView';
export const AvatarEditorPaletteSetView: FC<{ export const AvatarEditorPaletteSetView: FC<{
category: IAvatarEditorCategory, category: IAvatarEditorCategory;
paletteIndex: number; paletteIndex: number;
columnCount: number;
}> = props => }> = props =>
{ {
const { category = null, paletteIndex = -1 } = props; const { category = null, paletteIndex = -1, columnCount = 3 } = props;
const { selectedColorParts = null, selectEditorColor = null } = useAvatarEditor(); const { selectedColorParts = null, selectEditorColor = null } = useAvatarEditor();
const isPartColorSelected = (partColor: IPartColor) => const isPartColorSelected = (partColor: IPartColor) =>
@ -23,13 +24,13 @@ export const AvatarEditorPaletteSetView: FC<{
} }
return ( return (
<InfiniteGrid rows={ category.colorItems[paletteIndex] } columnCount={ 5 } overscan={ 5 } itemRender={ (item: IPartColor) => <InfiniteGrid columnCount={ columnCount } itemRender={ (item: IPartColor) =>
{ {
if(!item) return null; if(!item) return null;
return ( return (
<AvatarEditorPaletteSetItem setType={ category.setType } partColor={ item } isSelected={ isPartColorSelected(item) } onClick={ event => selectEditorColor(category.setType, paletteIndex, item.id) } /> <AvatarEditorPaletteSetItem isSelected={ isPartColorSelected(item) } partColor={ item } setType={ category.setType } width={ `calc(100% / ${ columnCount }` } onClick={ event => selectEditorColor(category.setType, paletteIndex, item.id) } />
) )
} } /> } } overscan={ columnCount } rows={ category.colorItems[paletteIndex] } />
); );
} }

View File

@ -88,9 +88,9 @@ export const CameraWidgetView: FC<{}> = props =>
return ( return (
<> <>
{ (mode === MODE_CAPTURE) && <CameraWidgetCaptureView onClose={ () => processAction('close') } onEdit={ () => processAction('edit') } onDelete={ () => processAction('delete') } /> } { (mode === MODE_CAPTURE) && <CameraWidgetCaptureView onClose={ () => processAction('close') } onDelete={ () => processAction('delete') } onEdit={ () => processAction('edit') } /> }
{ (mode === MODE_EDITOR) && <CameraWidgetEditorView picture={ cameraRoll[selectedPictureIndex] } myLevel={ myLevel } onClose={ () => processAction('close') } onCancel={ () => processAction('editor_cancel') } onCheckout={ checkoutPictureUrl } availableEffects={ availableEffects } /> } { (mode === MODE_EDITOR) && <CameraWidgetEditorView availableEffects={ availableEffects } myLevel={ myLevel } picture={ cameraRoll[selectedPictureIndex] } onCancel={ () => processAction('editor_cancel') } onCheckout={ checkoutPictureUrl } onClose={ () => processAction('close') } /> }
{ (mode === MODE_CHECKOUT) && <CameraWidgetCheckoutView base64Url={ base64Url } onCloseClick={ () => processAction('close') } onCancelClick={ () => processAction('editor_cancel') } price={ price }></CameraWidgetCheckoutView> } { (mode === MODE_CHECKOUT) && <CameraWidgetCheckoutView base64Url={ base64Url } price={ price } onCancelClick={ () => processAction('editor_cancel') } onCloseClick={ () => processAction('close') }></CameraWidgetCheckoutView> }
</> </>
); );
} }

View File

@ -2,7 +2,7 @@ import { GetRoomEngine, NitroRectangle, TextureUtils } from '@nitrots/nitro-rend
import { FC, useRef } from 'react'; import { FC, useRef } from 'react';
import { FaTimes } from 'react-icons/fa'; import { FaTimes } from 'react-icons/fa';
import { CameraPicture, GetRoomSession, LocalizeText, PlaySound, SoundNames } from '../../../api'; import { CameraPicture, GetRoomSession, LocalizeText, PlaySound, SoundNames } from '../../../api';
import { Column, DraggableWindow, Flex } from '../../../common'; import { Column, DraggableWindow } from '../../../common';
import { useCamera, useNotification } from '../../../hooks'; import { useCamera, useNotification } from '../../../hooks';
export interface CameraWidgetCaptureViewProps export interface CameraWidgetCaptureViewProps
@ -73,17 +73,17 @@ export const CameraWidgetCaptureView: FC<CameraWidgetCaptureViewProps> = props =
<button className="btn btn-danger" onClick={ onDelete }>{ LocalizeText('camera.delete.button.text') }</button> <button className="btn btn-danger" onClick={ onDelete }>{ LocalizeText('camera.delete.button.text') }</button>
</div> </div>
</div> } </div> }
<div className="d-flex justify-content-center"> <div className="flex justify-center">
<div className="camera-button" title={ LocalizeText('camera.take.photo.button.tooltip') } onClick={ takePicture } /> <div className="camera-button" title={ LocalizeText('camera.take.photo.button.tooltip') } onClick={ takePicture } />
</div> </div>
</div> </div>
{ (cameraRoll.length > 0) && { (cameraRoll.length > 0) &&
<Flex gap={ 2 } justifyContent="center" className="camera-roll d-flex justify-content-center py-2"> <div className="camera-roll flex justify-center py-2">
{ cameraRoll.map((picture, index) => { cameraRoll.map((picture, index) =>
{ {
return <img alt="" key={ index } src={ picture.imageUrl } onClick={ event => setSelectedPictureIndex(index) } />; return <img key={ index } alt="" src={ picture.imageUrl } onClick={ event => setSelectedPictureIndex(index) } />;
}) } }) }
</Flex> } </div> }
</Column> </Column>
</DraggableWindow> </DraggableWindow>
); );

View File

@ -1,7 +1,7 @@
import { CameraPublishStatusMessageEvent, CameraPurchaseOKMessageEvent, CameraStorageUrlMessageEvent, CreateLinkEvent, GetRoomEngine, PublishPhotoMessageComposer, PurchasePhotoMessageComposer } from '@nitrots/nitro-renderer'; import { CameraPublishStatusMessageEvent, CameraPurchaseOKMessageEvent, CameraStorageUrlMessageEvent, CreateLinkEvent, GetRoomEngine, PublishPhotoMessageComposer, PurchasePhotoMessageComposer } from '@nitrots/nitro-renderer';
import { FC, useEffect, useMemo, useState } from 'react'; import { FC, useEffect, useMemo, useState } from 'react';
import { GetConfigurationValue, LocalizeText, SendMessageComposer } from '../../../api'; import { GetConfigurationValue, LocalizeText, SendMessageComposer } from '../../../api';
import { Button, Column, Flex, LayoutCurrencyIcon, LayoutImage, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../common'; import { Button, Column, LayoutCurrencyIcon, LayoutImage, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../common';
import { useMessageEvent } from '../../../hooks'; import { useMessageEvent } from '../../../hooks';
export interface CameraWidgetCheckoutViewProps export interface CameraWidgetCheckoutViewProps
@ -85,46 +85,46 @@ export const CameraWidgetCheckoutView: FC<CameraWidgetCheckoutViewProps> = props
<NitroCardView className="nitro-camera-checkout" theme="primary-slim"> <NitroCardView className="nitro-camera-checkout" theme="primary-slim">
<NitroCardHeaderView headerText={ LocalizeText('camera.confirm_phase.title') } onCloseClick={ event => processAction('close') } /> <NitroCardHeaderView headerText={ LocalizeText('camera.confirm_phase.title') } onCloseClick={ event => processAction('close') } />
<NitroCardContentView> <NitroCardContentView>
<Flex center> <div className="flex items-center justify-center">
{ (pictureUrl && pictureUrl.length) && { (pictureUrl && pictureUrl.length) &&
<LayoutImage className="picture-preview border" imageUrl={ pictureUrl } /> } <LayoutImage className="picture-preview border" imageUrl={ pictureUrl } /> }
{ (!pictureUrl || !pictureUrl.length) && { (!pictureUrl || !pictureUrl.length) &&
<Flex center className="picture-preview border"> <div className="flex items-center justify-center picture-preview border">
<Text bold>{ LocalizeText('camera.loading') }</Text> <Text bold>{ LocalizeText('camera.loading') }</Text>
</Flex> } </div> }
</Flex> </div>
<Flex justifyContent="between" alignItems="center" className="bg-muted rounded p-2"> <div className="flex items-center bg-muted rounded p-2 justify-content-between">
<Column size={ publishDisabled ? 10 : 6 } gap={ 1 }> <Column gap={ 1 } size={ publishDisabled ? 10 : 6 }>
<Text bold> <Text bold>
{ LocalizeText('camera.purchase.header') } { LocalizeText('camera.purchase.header') }
</Text> </Text>
{ ((price.credits > 0) || (price.duckets > 0)) && { ((price.credits > 0) || (price.duckets > 0)) &&
<Flex gap={ 1 }> <div className="flex gap-1">
<Text>{ LocalizeText('catalog.purchase.confirmation.dialog.cost') }</Text> <Text>{ LocalizeText('catalog.purchase.confirmation.dialog.cost') }</Text>
{ (price.credits > 0) && { (price.credits > 0) &&
<Flex gap={ 1 }> <div className="flex gap-1">
<Text bold>{ price.credits }</Text> <Text bold>{ price.credits }</Text>
<LayoutCurrencyIcon type={ -1 } /> <LayoutCurrencyIcon type={ -1 } />
</Flex> } </div> }
{ (price.duckets > 0) && { (price.duckets > 0) &&
<Flex gap={ 1 }> <div className="flex gap-1">
<Text bold>{ price.duckets }</Text> <Text bold>{ price.duckets }</Text>
<LayoutCurrencyIcon type={ 5 } /> <LayoutCurrencyIcon type={ 5 } />
</Flex> } </div> }
</Flex> } </div> }
{ (picturesBought > 0) && { (picturesBought > 0) &&
<Text> <Text>
<Text bold>{ LocalizeText('camera.purchase.count.info') }</Text> { picturesBought } <Text bold>{ LocalizeText('camera.purchase.count.info') }</Text> { picturesBought }
<u className="ms-1 cursor-pointer" onClick={ () => CreateLinkEvent('inventory/toggle') }>{ LocalizeText('camera.open.inventory') }</u> <u className="ms-1 cursor-pointer" onClick={ () => CreateLinkEvent('inventory/toggle') }>{ LocalizeText('camera.open.inventory') }</u>
</Text> } </Text> }
</Column> </Column>
<Flex alignItems="center"> <div className="flex items-center">
<Button variant="success" disabled={ isWaiting } onClick={ event => processAction('buy') }>{ LocalizeText(!picturesBought ? 'buy' : 'camera.buy.another.button.text') }</Button> <Button disabled={ isWaiting } variant="success" onClick={ event => processAction('buy') }>{ LocalizeText(!picturesBought ? 'buy' : 'camera.buy.another.button.text') }</Button>
</Flex> </div>
</Flex> </div>
{ !publishDisabled && { !publishDisabled &&
<Flex justifyContent="between" alignItems="center" className="bg-muted rounded p-2"> <div className="flex items-center justify-content-between bg-muted rounded p-2">
<Column gap={ 1 }> <div className="flex flex-column gap-1">
<Text bold> <Text bold>
{ LocalizeText(wasPicturePublished ? 'camera.publish.successful' : 'camera.publish.explanation') } { LocalizeText(wasPicturePublished ? 'camera.publish.successful' : 'camera.publish.explanation') }
</Text> </Text>
@ -133,26 +133,26 @@ export const CameraWidgetCheckoutView: FC<CameraWidgetCheckoutViewProps> = props
</Text> </Text>
{ wasPicturePublished && <a href={ publishUrl } rel="noreferrer" target="_blank">{ LocalizeText('camera.link.to.published') }</a> } { wasPicturePublished && <a href={ publishUrl } rel="noreferrer" target="_blank">{ LocalizeText('camera.link.to.published') }</a> }
{ !wasPicturePublished && (price.publishDucketPrice > 0) && { !wasPicturePublished && (price.publishDucketPrice > 0) &&
<Flex gap={ 1 }> <div className="flex gap-1">
<Text>{ LocalizeText('catalog.purchase.confirmation.dialog.cost') }</Text> <Text>{ LocalizeText('catalog.purchase.confirmation.dialog.cost') }</Text>
<Flex gap={ 1 }> <div className="flex gap-1">
<Text bold>{ price.publishDucketPrice }</Text> <Text bold>{ price.publishDucketPrice }</Text>
<LayoutCurrencyIcon type={ 5 } /> <LayoutCurrencyIcon type={ 5 } />
</Flex> </div>
</Flex> } </div> }
{ (publishCooldown > 0) && <div className="mt-1 text-center fw-bold">{ LocalizeText('camera.publish.wait', [ 'minutes' ], [ Math.ceil( publishCooldown / 60).toString() ]) }</div> } { (publishCooldown > 0) && <div className="mt-1 text-center fw-bold">{ LocalizeText('camera.publish.wait', [ 'minutes' ], [ Math.ceil( publishCooldown / 60).toString() ]) }</div> }
</Column> </div>
{ !wasPicturePublished && { !wasPicturePublished &&
<Flex className="d-flex align-items-end"> <div className="flex align-items-end">
<Button variant="success" disabled={ (isWaiting || (publishCooldown > 0)) } onClick={ event => processAction('publish') }> <Button disabled={ (isWaiting || (publishCooldown > 0)) } variant="success" onClick={ event => processAction('publish') }>
{ LocalizeText('camera.publish.button.text') } { LocalizeText('camera.publish.button.text') }
</Button> </Button>
</Flex> } </div> }
</Flex> } </div> }
<Text center>{ LocalizeText('camera.warning.disclaimer') }</Text> <Text center>{ LocalizeText('camera.warning.disclaimer') }</Text>
<Flex justifyContent="end"> <div className="flex justify-content-end">
<Button onClick={ event => processAction('cancel') }>{ LocalizeText('generic.cancel') }</Button> <Button onClick={ event => processAction('cancel') }>{ LocalizeText('generic.cancel') }</Button>
</Flex> </div>
</NitroCardContentView> </NitroCardContentView>
</NitroCardView> </NitroCardView>
); );

View File

@ -55,10 +55,10 @@ export const CameraWidgetShowPhotoView: FC<CameraWidgetShowPhotoViewProps> = pro
</Flex> </Flex>
{ currentImage.m && currentImage.m.length && { currentImage.m && currentImage.m.length &&
<Text center>{ currentImage.m }</Text> } <Text center>{ currentImage.m }</Text> }
<Flex alignItems="center" justifyContent="between"> <div className="flex items-center justify-content-between">
<Text>{ (currentImage.n || '') }</Text> <Text>{ (currentImage.n || '') }</Text>
<Text>{ new Date(currentImage.t * 1000).toLocaleDateString() }</Text> <Text>{ new Date(currentImage.t * 1000).toLocaleDateString() }</Text>
</Flex> </div>
{ (currentPhotos.length > 1) && { (currentPhotos.length > 1) &&
<Flex className="picture-preview-buttons"> <Flex className="picture-preview-buttons">
<FaArrowLeft className="cursor-pointer picture-preview-buttons-previous fa-icon" onClick={ previous } /> <FaArrowLeft className="cursor-pointer picture-preview-buttons-previous fa-icon" onClick={ previous } />

View File

@ -3,7 +3,7 @@ import { FC, useCallback, useEffect, useMemo, useState } from 'react';
import { FaSave, FaSearchMinus, FaSearchPlus, FaTrash } from 'react-icons/fa'; import { FaSave, FaSearchMinus, FaSearchPlus, FaTrash } from 'react-icons/fa';
import ReactSlider from 'react-slider'; import ReactSlider from 'react-slider';
import { CameraEditorTabs, CameraPicture, CameraPictureThumbnail, LocalizeText } from '../../../../api'; import { CameraEditorTabs, CameraPicture, CameraPictureThumbnail, LocalizeText } from '../../../../api';
import { Button, ButtonGroup, Column, Flex, Grid, LayoutImage, NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView, Text } from '../../../../common'; import { Button, Column, Grid, LayoutImage, NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView, Text } from '../../../../common';
import { CameraWidgetEffectListView } from './effect-list'; import { CameraWidgetEffectListView } from './effect-list';
export interface CameraWidgetEditorViewProps export interface CameraWidgetEditorViewProps
@ -194,27 +194,27 @@ export const CameraWidgetEditorView: FC<CameraWidgetEditorViewProps> = props =>
</NitroCardTabsView> </NitroCardTabsView>
<NitroCardContentView> <NitroCardContentView>
<Grid> <Grid>
<Column size={ 5 } overflow="hidden"> <Column overflow="hidden" size={ 5 }>
<CameraWidgetEffectListView myLevel={ myLevel } selectedEffects={ selectedEffects } effects={ getEffectList() } thumbnails={ effectsThumbnails } processAction={ processAction } /> <CameraWidgetEffectListView effects={ getEffectList() } myLevel={ myLevel } processAction={ processAction } selectedEffects={ selectedEffects } thumbnails={ effectsThumbnails } />
</Column> </Column>
<Column size={ 7 } justifyContent="between" overflow="hidden"> <Column justifyContent="between" overflow="hidden" size={ 7 }>
<Column center> <Column center>
<LayoutImage imageUrl={ currentPictureUrl } className="picture-preview" /> <LayoutImage className="picture-preview" imageUrl={ currentPictureUrl } />
{ selectedEffectName && { selectedEffectName &&
<Column center fullWidth gap={ 1 }> <Column center fullWidth gap={ 1 }>
<Text>{ LocalizeText('camera.effect.name.' + selectedEffectName) }</Text> <Text>{ LocalizeText('camera.effect.name.' + selectedEffectName) }</Text>
<ReactSlider <ReactSlider
className={ 'nitro-slider' } className={ 'nitro-slider' }
min={ 0 }
max={ 1 } max={ 1 }
min={ 0 }
renderThumb={ (props, state) => <div { ...props }>{ state.valueNow }</div> }
step={ 0.01 } step={ 0.01 }
value={ getCurrentEffect.alpha } value={ getCurrentEffect.alpha }
onChange={ event => setSelectedEffectAlpha(event) } onChange={ event => setSelectedEffectAlpha(event) } />
renderThumb={ (props, state) => <div { ...props }>{ state.valueNow }</div> } />
</Column> } </Column> }
</Column> </Column>
<Flex justifyContent="between"> <div className="flex justify-content-between">
<ButtonGroup> <div className="btn-group">
<Button onClick={ event => processAction('clear_effects') }> <Button onClick={ event => processAction('clear_effects') }>
<FaTrash className="fa-icon" /> <FaTrash className="fa-icon" />
</Button> </Button>
@ -225,16 +225,16 @@ export const CameraWidgetEditorView: FC<CameraWidgetEditorViewProps> = props =>
{ isZoomed && <FaSearchMinus className="fa-icon" /> } { isZoomed && <FaSearchMinus className="fa-icon" /> }
{ !isZoomed && <FaSearchPlus className="fa-icon" /> } { !isZoomed && <FaSearchPlus className="fa-icon" /> }
</Button> </Button>
</ButtonGroup> </div>
<Flex gap={ 1 }> <div className="flex gap-1">
<Button onClick={ event => processAction('cancel') }> <Button onClick={ event => processAction('cancel') }>
{ LocalizeText('generic.cancel') } { LocalizeText('generic.cancel') }
</Button> </Button>
<Button onClick={ event => processAction('checkout') }> <Button onClick={ event => processAction('checkout') }>
{ LocalizeText('camera.preview.button.text') } { LocalizeText('camera.preview.button.text') }
</Button> </Button>
</Flex> </div>
</Flex> </div>
</Column> </Column>
</Grid> </Grid>
</NitroCardContentView> </NitroCardContentView>

View File

@ -19,9 +19,9 @@ export const CameraWidgetEffectListItemView: FC<CameraWidgetEffectListItemViewPr
const { effect = null, thumbnailUrl = null, isActive = false, isLocked = false, selectEffect = null, removeEffect = null } = props; const { effect = null, thumbnailUrl = null, isActive = false, isLocked = false, selectEffect = null, removeEffect = null } = props;
return ( return (
<LayoutGridItem title={ LocalizeText(!isLocked ? (`camera.effect.name.${ effect.name }`) : `camera.effect.required.level ${ effect.minLevel }`) } itemActive={ isActive } onClick={ event => (!isActive && selectEffect()) }> <LayoutGridItem itemActive={ isActive } title={ LocalizeText(!isLocked ? (`camera.effect.name.${ effect.name }`) : `camera.effect.required.level ${ effect.minLevel }`) } onClick={ event => (!isActive && selectEffect()) }>
{ isActive && { isActive &&
<Button variant="danger" className="rounded-circle remove-effect" onClick={ removeEffect }> <Button className="rounded-circle remove-effect" variant="danger" onClick={ removeEffect }>
<FaTimes className="fa-icon" /> <FaTimes className="fa-icon" />
</Button> } </Button> }
{ !isLocked && (thumbnailUrl && thumbnailUrl.length > 0) && { !isLocked && (thumbnailUrl && thumbnailUrl.length > 0) &&
@ -29,7 +29,7 @@ export const CameraWidgetEffectListItemView: FC<CameraWidgetEffectListItemViewPr
<img alt="" src={ thumbnailUrl } /> <img alt="" src={ thumbnailUrl } />
</div> } </div> }
{ isLocked && { isLocked &&
<Text center bold> <Text bold center>
<div> <div>
<FaLock className="fa-icon" /> <FaLock className="fa-icon" />
</div> </div>

View File

@ -24,7 +24,7 @@ export const CameraWidgetEffectListView: FC<CameraWidgetEffectListViewProps> = p
const thumbnailUrl = (thumbnails.find(thumbnail => (thumbnail.effectName === effect.name))); const thumbnailUrl = (thumbnails.find(thumbnail => (thumbnail.effectName === effect.name)));
const isActive = (selectedEffects.findIndex(selectedEffect => (selectedEffect.effect.name === effect.name)) > -1); const isActive = (selectedEffects.findIndex(selectedEffect => (selectedEffect.effect.name === effect.name)) > -1);
return <CameraWidgetEffectListItemView key={ index } effect={ effect } thumbnailUrl={ ((thumbnailUrl && thumbnailUrl.thumbnailUrl) || null) } isActive={ isActive } isLocked={ (effect.minLevel > myLevel) } selectEffect={ () => processAction('select_effect', effect.name) } removeEffect={ () => processAction('remove_effect', effect.name) } /> return <CameraWidgetEffectListItemView key={ index } effect={ effect } isActive={ isActive } isLocked={ (effect.minLevel > myLevel) } removeEffect={ () => processAction('remove_effect', effect.name) } selectEffect={ () => processAction('select_effect', effect.name) } thumbnailUrl={ ((thumbnailUrl && thumbnailUrl.thumbnailUrl) || null) } />
}) } }) }
</Grid> </Grid>
); );

View File

@ -1,7 +1,7 @@
import { GetRoomEngine, GetSessionDataManager } from '@nitrots/nitro-renderer'; import { GetRoomEngine, GetSessionDataManager } from '@nitrots/nitro-renderer';
import { FC } from 'react'; import { FC } from 'react';
import { CalendarItemState, GetConfigurationValue, ICalendarItem } from '../../api'; import { CalendarItemState, GetConfigurationValue, ICalendarItem } from '../../api';
import { Base, Column, Flex, LayoutImage } from '../../common'; import { Column, Flex, LayoutImage } from '../../common';
interface CalendarItemViewProps interface CalendarItemViewProps
{ {
@ -33,7 +33,7 @@ export const CalendarItemView: FC<CalendarItemViewProps> = props =>
} }
return ( return (
<Column fit center pointer className={ `campaign-spritesheet campaign-day-generic-bg rounded calendar-item ${ active ? 'active' : '' }` } onClick={ () => onClick(itemId) }> <Column center fit pointer className={ `campaign-spritesheet campaign-day-generic-bg rounded calendar-item ${ active ? 'active' : '' }` } onClick={ () => onClick(itemId) }>
{ (state === CalendarItemState.STATE_UNLOCKED) && { (state === CalendarItemState.STATE_UNLOCKED) &&
<Flex center className="campaign-spritesheet unlocked-bg"> <Flex center className="campaign-spritesheet unlocked-bg">
<Flex center className="campaign-spritesheet campaign-opened"> <Flex center className="campaign-spritesheet campaign-opened">
@ -44,9 +44,9 @@ export const CalendarItemView: FC<CalendarItemViewProps> = props =>
{ (state !== CalendarItemState.STATE_UNLOCKED) && { (state !== CalendarItemState.STATE_UNLOCKED) &&
<Flex center className="campaign-spritesheet locked-bg"> <Flex center className="campaign-spritesheet locked-bg">
{ (state === CalendarItemState.STATE_LOCKED_AVAILABLE) && { (state === CalendarItemState.STATE_LOCKED_AVAILABLE) &&
<Base className="campaign-spritesheet available" /> } <div className="campaign-spritesheet available" /> }
{ ((state === CalendarItemState.STATE_LOCKED_EXPIRED) || (state === CalendarItemState.STATE_LOCKED_FUTURE)) && { ((state === CalendarItemState.STATE_LOCKED_EXPIRED) || (state === CalendarItemState.STATE_LOCKED_FUTURE)) &&
<Base className="campaign-spritesheet unavailable" /> } <div className="campaign-spritesheet unavailable" /> }
</Flex> } </Flex> }
</Column> </Column>
); );

View File

@ -1,7 +1,7 @@
import { GetSessionDataManager } from '@nitrots/nitro-renderer'; import { GetSessionDataManager } from '@nitrots/nitro-renderer';
import { FC, useState } from 'react'; import { FC, useState } from 'react';
import { CalendarItemState, ICalendarItem, LocalizeText } from '../../api'; import { CalendarItemState, ICalendarItem, LocalizeText } from '../../api';
import { Base, Button, Column, Flex, Grid, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../common'; import { Button, Column, Grid, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../common';
import { CalendarItemView } from './CalendarItemView'; import { CalendarItemView } from './CalendarItemView';
interface CalendarViewProps interface CalendarViewProps
@ -100,26 +100,26 @@ export const CalendarView: FC<CalendarViewProps> = props =>
<NitroCardView className="nitro-campaign-calendar" theme="primary-slim"> <NitroCardView className="nitro-campaign-calendar" theme="primary-slim">
<NitroCardHeaderView headerText={ LocalizeText(`campaign.calendar.${ campaignName }.title`) } onCloseClick={ onClose } /> <NitroCardHeaderView headerText={ LocalizeText(`campaign.calendar.${ campaignName }.title`) } onCloseClick={ onClose } />
<NitroCardContentView> <NitroCardContentView>
<Grid fullHeight={ false } justifyContent="between" alignItems="center"> <Grid alignItems="center" fullHeight={ false } justifyContent="between">
<Column size={ 1 } /> <Column size={ 1 } />
<Column size={ 10 }> <Column size={ 10 }>
<Flex justifyContent="between" alignItems="center" gap={ 1 }> <div className="flex items-center gap-1 justify-content-between">
<Column gap={ 1 }> <div className="flex flex-column gap-1">
<Text fontSize={ 3 }>{ LocalizeText('campaign.calendar.heading.day', [ 'number' ], [ (selectedDay + 1).toString() ]) }</Text> <Text fontSize={ 3 }>{ LocalizeText('campaign.calendar.heading.day', [ 'number' ], [ (selectedDay + 1).toString() ]) }</Text>
<Text>{ dayMessage(selectedDay) }</Text> <Text>{ dayMessage(selectedDay) }</Text>
</Column> </div>
<div> <div>
{ GetSessionDataManager().isModerator && { GetSessionDataManager().isModerator &&
<Button variant="danger" onClick={ forceOpen }>Force open</Button> } <Button variant="danger" onClick={ forceOpen }>Force open</Button> }
</div> </div>
</Flex> </div>
</Column> </Column>
<Column size={ 1 } /> <Column size={ 1 } />
</Grid> </Grid>
<Flex fullHeight gap={ 2 }> <div className="flex h-100 gap-2">
<Flex center> <div className="flex items-center justify-center">
<Base pointer className="campaign-spritesheet prev" onClick={ onClickPrev } /> <div className="campaign-spritesheet prev cursor-pointer" onClick={ onClickPrev } />
</Flex> </div>
<Column center fullWidth> <Column center fullWidth>
<Grid fit columnCount={ TOTAL_SHOWN_ITEMS } gap={ 1 }> <Grid fit columnCount={ TOTAL_SHOWN_ITEMS } gap={ 1 }>
{ [ ...Array(TOTAL_SHOWN_ITEMS) ].map((e, i) => { [ ...Array(TOTAL_SHOWN_ITEMS) ].map((e, i) =>
@ -128,16 +128,16 @@ export const CalendarView: FC<CalendarViewProps> = props =>
return ( return (
<Column key={ i } overflow="hidden"> <Column key={ i } overflow="hidden">
<CalendarItemView itemId={ day } state={ getDayState(day) } active={ (selectedDay === day) } product={ receivedProducts.has(day) ? receivedProducts.get(day) : null } onClick={ onClickItem } /> <CalendarItemView active={ (selectedDay === day) } itemId={ day } product={ receivedProducts.has(day) ? receivedProducts.get(day) : null } state={ getDayState(day) } onClick={ onClickItem } />
</Column> </Column>
); );
}) } }) }
</Grid> </Grid>
</Column> </Column>
<Flex center> <div className="flex items-center justify-center">
<Base pointer className="campaign-spritesheet next" onClick={ onClickNext } /> <div className="campaign-spritesheet next cursor-pointer" onClick={ onClickNext } />
</Flex> </div>
</Flex> </div>
</NitroCardContentView> </NitroCardContentView>
</NitroCardView> </NitroCardView>
) )

View File

@ -94,7 +94,7 @@ export const CampaignView: FC<{}> = props =>
return ( return (
<> <>
{ (calendarData && isCalendarOpen) && { (calendarData && isCalendarOpen) &&
<CalendarView onClose={ () => setCalendarOpen(false) } campaignName={ calendarData.campaignName } currentDay={ calendarData.currentDay } numDays={ calendarData.campaignDays } openedDays={ calendarData.openedDays } missedDays={ calendarData.missedDays } openPackage={ openPackage } receivedProducts={ receivedProducts } /> <CalendarView campaignName={ calendarData.campaignName } currentDay={ calendarData.currentDay } missedDays={ calendarData.missedDays } numDays={ calendarData.campaignDays } openedDays={ calendarData.openedDays } openPackage={ openPackage } receivedProducts={ receivedProducts } onClose={ () => setCalendarOpen(false) } />
} }
</> </>
) )

View File

@ -1,7 +1,7 @@
import { AddLinkEventTracker, ILinkEventTracker, RemoveLinkEventTracker } from '@nitrots/nitro-renderer'; import { AddLinkEventTracker, ILinkEventTracker, RemoveLinkEventTracker } from '@nitrots/nitro-renderer';
import { FC, useEffect } from 'react'; import { FC, useEffect } from 'react';
import { GetConfigurationValue, LocalizeText } from '../../api'; import { GetConfigurationValue, LocalizeText } from '../../api';
import { Column, Flex, Grid, NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView } from '../../common'; import { Column, Grid, NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView } from '../../common';
import { useCatalog } from '../../hooks'; import { useCatalog } from '../../hooks';
import { CatalogIconView } from './views/catalog-icon/CatalogIconView'; import { CatalogIconView } from './views/catalog-icon/CatalogIconView';
import { CatalogGiftView } from './views/gift/CatalogGiftView'; import { CatalogGiftView } from './views/gift/CatalogGiftView';
@ -69,7 +69,7 @@ export const CatalogView: FC<{}> = props =>
return ( return (
<> <>
{ isVisible && { isVisible &&
<NitroCardView uniqueKey="catalog" className="nitro-catalog" style={ GetConfigurationValue('catalog.headers') ? { width: 710 } : {} }> <NitroCardView className="nitro-catalog" style={ GetConfigurationValue('catalog.headers') ? { width: 710 } : {} } uniqueKey="catalog">
<NitroCardHeaderView headerText={ LocalizeText('catalog.title') } onCloseClick={ event => setIsVisible(false) } /> <NitroCardHeaderView headerText={ LocalizeText('catalog.title') } onCloseClick={ event => setIsVisible(false) } />
<NitroCardTabsView> <NitroCardTabsView>
{ rootNode && (rootNode.children.length > 0) && rootNode.children.map(child => { rootNode && (rootNode.children.length > 0) && rootNode.children.map(child =>
@ -83,10 +83,10 @@ export const CatalogView: FC<{}> = props =>
activateNode(child); activateNode(child);
} } > } } >
<Flex gap={ GetConfigurationValue('catalog.tab.icons') ? 1 : 0 } alignItems="center"> <div className={ `flex items-center gap-${ GetConfigurationValue('catalog.tab.icons') ? 1 : 0 }` }>
{ GetConfigurationValue('catalog.tab.icons') && <CatalogIconView icon={ child.iconId } /> } { GetConfigurationValue('catalog.tab.icons') && <CatalogIconView icon={ child.iconId } /> }
{ child.localization } { child.localization }
</Flex> </div>
</NitroCardTabsItemView> </NitroCardTabsItemView>
); );
}) } }) }
@ -94,11 +94,11 @@ export const CatalogView: FC<{}> = props =>
<NitroCardContentView> <NitroCardContentView>
<Grid> <Grid>
{ !navigationHidden && { !navigationHidden &&
<Column size={ 3 } overflow="hidden"> <Column overflow="hidden" size={ 3 }>
{ activeNodes && (activeNodes.length > 0) && { activeNodes && (activeNodes.length > 0) &&
<CatalogNavigationView node={ activeNodes[0] } /> } <CatalogNavigationView node={ activeNodes[0] } /> }
</Column> } </Column> }
<Column size={ !navigationHidden ? 9 : 12 } overflow="hidden"> <Column overflow="hidden" size={ !navigationHidden ? 9 : 12 }>
{ GetCatalogLayout(currentPage, () => setNavigationHidden(true)) } { GetCatalogLayout(currentPage, () => setNavigationHidden(true)) }
</Column> </Column>
</Grid> </Grid>

View File

@ -1,6 +1,5 @@
import { FC, useEffect, useState } from 'react'; import { FC, useEffect, useState } from 'react';
import { GetConfigurationValue } from '../../../../api'; import { GetConfigurationValue } from '../../../../api';
import { Flex } from '../../../../common';
export interface CatalogHeaderViewProps export interface CatalogHeaderViewProps
{ {
@ -17,10 +16,10 @@ export const CatalogHeaderView: FC<CatalogHeaderViewProps> = props =>
setDisplayImageUrl(imageUrl ?? GetConfigurationValue<string>('catalog.asset.image.url').replace('%name%', 'catalog_header_roombuilder')); setDisplayImageUrl(imageUrl ?? GetConfigurationValue<string>('catalog.asset.image.url').replace('%name%', 'catalog_header_roombuilder'));
}, [ imageUrl ]); }, [ imageUrl ]);
return <Flex center fullWidth className="nitro-catalog-header"> return <div className="flex justify-center items-center w-100 nitro-catalog-header">
<img src={ displayImageUrl } onError={ ({ currentTarget }) => <img src={ displayImageUrl } onError={ ({ currentTarget }) =>
{ {
currentTarget.src = GetConfigurationValue<string>('catalog.asset.image.url').replace('%name%', 'catalog_header_roombuilder'); currentTarget.src = GetConfigurationValue<string>('catalog.asset.image.url').replace('%name%', 'catalog_header_roombuilder');
} } /> } } />
</Flex>; </div>;
} }

View File

@ -2,7 +2,7 @@ import { GetSessionDataManager, GiftReceiverNotFoundEvent, PurchaseFromCatalogAs
import { ChangeEvent, FC, useCallback, useEffect, useMemo, useState } from 'react'; import { ChangeEvent, FC, useCallback, useEffect, useMemo, useState } from 'react';
import { FaChevronLeft, FaChevronRight } from 'react-icons/fa'; import { FaChevronLeft, FaChevronRight } from 'react-icons/fa';
import { ColorUtils, LocalizeText, MessengerFriend, ProductTypeEnum, SendMessageComposer } from '../../../../api'; import { ColorUtils, LocalizeText, MessengerFriend, ProductTypeEnum, SendMessageComposer } from '../../../../api';
import { Base, Button, ButtonGroup, Column, Flex, FormGroup, LayoutCurrencyIcon, LayoutFurniImageView, LayoutGiftTagView, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text, classNames } from '../../../../common'; import { Button, Column, Flex, FormGroup, LayoutCurrencyIcon, LayoutFurniImageView, LayoutGiftTagView, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text, classNames } from '../../../../common';
import { CatalogEvent, CatalogInitGiftEvent, CatalogPurchasedEvent } from '../../../../events'; import { CatalogEvent, CatalogInitGiftEvent, CatalogPurchasedEvent } from '../../../../events';
import { useCatalog, useFriends, useMessageEvent, useUiEvent } from '../../../../hooks'; import { useCatalog, useFriends, useMessageEvent, useUiEvent } from '../../../../hooks';
@ -210,79 +210,79 @@ export const CatalogGiftView: FC<{}> = props =>
const priceText = 'catalog.gift_wrapping_new.' + (isBoxDefault ? 'freeprice' : 'price'); const priceText = 'catalog.gift_wrapping_new.' + (isBoxDefault ? 'freeprice' : 'price');
return ( return (
<NitroCardView uniqueKey="catalog-gift" className="nitro-catalog-gift" theme="primary-slim"> <NitroCardView className="nitro-catalog-gift" theme="primary-slim" uniqueKey="catalog-gift">
<NitroCardHeaderView headerText={ LocalizeText('catalog.gift_wrapping.title') } onCloseClick={ onClose } /> <NitroCardHeaderView headerText={ LocalizeText('catalog.gift_wrapping.title') } onCloseClick={ onClose } />
<NitroCardContentView className="text-black"> <NitroCardContentView className="text-black">
<FormGroup column> <FormGroup column>
<Text>{ LocalizeText('catalog.gift_wrapping.receiver') }</Text> <Text>{ LocalizeText('catalog.gift_wrapping.receiver') }</Text>
<input type="text" className={ classNames('form-control form-control-sm', receiverNotFound && 'is-invalid') } value={ receiverName } onChange={ (e) => onTextChanged(e) } /> <input className={ classNames('form-control form-control-sm', receiverNotFound && 'is-invalid') } type="text" value={ receiverName } onChange={ (e) => onTextChanged(e) } />
{ (suggestions.length > 0 && isAutocompleteVisible) && { (suggestions.length > 0 && isAutocompleteVisible) &&
<Column className="autocomplete-gift-container"> <Column className="autocomplete-gift-container">
{ suggestions.map((friend: MessengerFriend) => ( { suggestions.map((friend: MessengerFriend) => (
<Base key={ friend.id } className="autocomplete-gift-item" onClick={ (e) => selectedReceiverName(friend.name) }>{ friend.name }</Base> <div key={ friend.id } className="autocomplete-gift-item" onClick={ (e) => selectedReceiverName(friend.name) }>{ friend.name }</div>
)) } )) }
</Column> </Column>
} }
{ receiverNotFound && { receiverNotFound &&
<Base className="invalid-feedback">{ LocalizeText('catalog.gift_wrapping.receiver_not_found.title') }</Base> } <div className="invalid-feedback">{ LocalizeText('catalog.gift_wrapping.receiver_not_found.title') }</div> }
</FormGroup> </FormGroup>
<LayoutGiftTagView figure={ GetSessionDataManager().figure } userName={ GetSessionDataManager().userName } message={ message } editable={ true } onChange={ (value) => setMessage(value) } /> <LayoutGiftTagView editable={ true } figure={ GetSessionDataManager().figure } message={ message } userName={ GetSessionDataManager().userName } onChange={ (value) => setMessage(value) } />
<Base className="form-check"> <div className="form-check">
<input className="form-check-input" type="checkbox" name="showMyFace" checked={ showMyFace } onChange={ (e) => setShowMyFace(value => !value) } /> <input checked={ showMyFace } className="form-check-input" name="showMyFace" type="checkbox" onChange={ (e) => setShowMyFace(value => !value) } />
<label className="form-check-label">{ LocalizeText('catalog.gift_wrapping.show_face.title') }</label> <label className="form-check-label">{ LocalizeText('catalog.gift_wrapping.show_face.title') }</label>
</Base> </div>
<Flex alignItems="center" gap={ 2 }> <div className="items-center gap-2">
{ selectedColorId && { selectedColorId &&
<Base className="gift-preview"> <div className="gift-preview">
<LayoutFurniImageView productType={ ProductTypeEnum.FLOOR } productClassId={ colourId } extraData={ boxExtraData } /> <LayoutFurniImageView extraData={ boxExtraData } productClassId={ colourId } productType={ ProductTypeEnum.FLOOR } />
</Base> } </div> }
<Column gap={ 1 }> <div className="flex flex-column gap-1">
<Flex gap={ 2 }> <div className="flex gap-2">
<ButtonGroup> <div className="btn-group">
<Button variant="primary" onClick={ () => handleAction('prev_box') }> <Button variant="primary" onClick={ () => handleAction('prev_box') }>
<FaChevronLeft className="fa-icon" /> <FaChevronLeft className="fa-icon" />
</Button> </Button>
<Button variant="primary" onClick={ () => handleAction('next_box') }> <Button variant="primary" onClick={ () => handleAction('next_box') }>
<FaChevronRight className="fa-icon" /> <FaChevronRight className="fa-icon" />
</Button> </Button>
</ButtonGroup> </div>
<Column gap={ 1 }> <div className="flex flex-column gap-1">
<Text fontWeight="bold">{ LocalizeText(boxName) }</Text> <Text fontWeight="bold">{ LocalizeText(boxName) }</Text>
<Flex alignItems="center" gap={ 1 }> <div className="flex items-center gap-1">
{ LocalizeText(priceText, [ 'price' ], [ giftConfiguration.price.toString() ]) } { LocalizeText(priceText, [ 'price' ], [ giftConfiguration.price.toString() ]) }
<LayoutCurrencyIcon type={ -1 } /> <LayoutCurrencyIcon type={ -1 } />
</Flex> </div>
</Column> </div>
</Flex> </div>
<Flex alignItems="center" gap={ 2 } className={ isColorable ? '' : 'opacity-50 pointer-events-none' }> <Flex alignItems="center" className={ isColorable ? '' : 'opacity-50 pointer-events-none' } gap={ 2 }>
<ButtonGroup> <div className="btn-group">
<Button variant="primary" onClick={ () => handleAction('prev_ribbon') }> <Button variant="primary" onClick={ () => handleAction('prev_ribbon') }>
<FaChevronLeft className="fa-icon" /> <FaChevronLeft className="fa-icon" />
</Button> </Button>
<Button variant="primary" onClick={ () => handleAction('next_ribbon') }> <Button variant="primary" onClick={ () => handleAction('next_ribbon') }>
<FaChevronRight className="fa-icon" /> <FaChevronRight className="fa-icon" />
</Button> </Button>
</ButtonGroup> </div>
<Text fontWeight="bold">{ LocalizeText(ribbonName) }</Text> <Text fontWeight="bold">{ LocalizeText(ribbonName) }</Text>
</Flex> </Flex>
</Column> </div>
</Flex> </div>
<Column gap={ 1 } className={ isColorable ? '' : 'opacity-50 pointer-events-none' }> <Column className={ isColorable ? '' : 'opacity-50 pointer-events-none' } gap={ 1 }>
<Text fontWeight="bold"> <Text fontWeight="bold">
{ LocalizeText('catalog.gift_wrapping.pick_color') } { LocalizeText('catalog.gift_wrapping.pick_color') }
</Text> </Text>
<ButtonGroup fullWidth> <div className="btn-group w-100">
{ colors.map(color => <Button key={ color.id } variant="dark" active={ (color.id === selectedColorId) } disabled={ !isColorable } style={ { backgroundColor: color.color } } onClick={ () => setSelectedColorId(color.id) } />) } { colors.map(color => <Button key={ color.id } active={ (color.id === selectedColorId) } disabled={ !isColorable } style={ { backgroundColor: color.color } } variant="dark" onClick={ () => setSelectedColorId(color.id) } />) }
</ButtonGroup> </div>
</Column> </Column>
<Flex justifyContent="between" alignItems="center"> <div className="flex justify-content-between items-center">
<Button variant="link" onClick={ onClose } className="text-black"> <Button className="text-black" variant="link" onClick={ onClose }>
{ LocalizeText('cancel') } { LocalizeText('cancel') }
</Button> </Button>
<Button variant="success" onClick={ () => handleAction('buy') }> <Button variant="success" onClick={ () => handleAction('buy') }>
{ LocalizeText('catalog.gift_wrapping.give_gift') } { LocalizeText('catalog.gift_wrapping.give_gift') }
</Button> </Button>
</Flex> </div>
</NitroCardContentView> </NitroCardContentView>
</NitroCardView> </NitroCardView>
); );

View File

@ -1,7 +1,7 @@
import { FC } from 'react'; import { FC } from 'react';
import { FaCaretDown, FaCaretUp } from 'react-icons/fa'; import { FaCaretDown, FaCaretUp } from 'react-icons/fa';
import { ICatalogNode } from '../../../../api'; import { ICatalogNode } from '../../../../api';
import { Base, LayoutGridItem, Text } from '../../../../common'; import { LayoutGridItem, Text } from '../../../../common';
import { useCatalog } from '../../../../hooks'; import { useCatalog } from '../../../../hooks';
import { CatalogIconView } from '../catalog-icon/CatalogIconView'; import { CatalogIconView } from '../catalog-icon/CatalogIconView';
import { CatalogNavigationSetView } from './CatalogNavigationSetView'; import { CatalogNavigationSetView } from './CatalogNavigationSetView';
@ -18,10 +18,10 @@ export const CatalogNavigationItemView: FC<CatalogNavigationItemViewProps> = pro
const { activateNode = null } = useCatalog(); const { activateNode = null } = useCatalog();
return ( return (
<Base className="nitro-catalog-navigation-section"> <div className="nitro-catalog-navigation-section">
<LayoutGridItem gap={ 1 } column={ false } itemActive={ node.isActive } onClick={ event => activateNode(node) } className={ child ? 'inset' : '' }> <LayoutGridItem className={ child ? 'inset' : '' } column={ false } gap={ 1 } itemActive={ node.isActive } onClick={ event => activateNode(node) }>
<CatalogIconView icon={ node.iconId } /> <CatalogIconView icon={ node.iconId } />
<Text grow truncate>{ node.localization }</Text> <Text truncate className="flex-grow-1">{ node.localization }</Text>
{ node.isBranch && { node.isBranch &&
<> <>
{ node.isOpen && <FaCaretUp className="fa-icon" /> } { node.isOpen && <FaCaretUp className="fa-icon" /> }
@ -29,7 +29,7 @@ export const CatalogNavigationItemView: FC<CatalogNavigationItemViewProps> = pro
</> } </> }
</LayoutGridItem> </LayoutGridItem>
{ node.isOpen && node.isBranch && { node.isOpen && node.isBranch &&
<CatalogNavigationSetView node={ node } child={ true } /> } <CatalogNavigationSetView child={ true } node={ node } /> }
</Base> </div>
); );
} }

View File

@ -18,7 +18,7 @@ export const CatalogNavigationSetView: FC<CatalogNavigationSetViewProps> = props
{ {
if(!n.isVisible) return null; if(!n.isVisible) return null;
return <CatalogNavigationItemView key={ index } node={ n } child={ child } /> return <CatalogNavigationItemView key={ index } child={ child } node={ n } />
}) } }) }
</> </>
); );

View File

@ -20,7 +20,7 @@ export const CatalogNavigationView: FC<CatalogNavigationViewProps> = props =>
<> <>
<CatalogSearchView /> <CatalogSearchView />
<Column fullHeight className="nitro-catalog-navigation-grid-container rounded p-1" overflow="hidden"> <Column fullHeight className="nitro-catalog-navigation-grid-container rounded p-1" overflow="hidden">
<AutoGrid id="nitro-catalog-main-navigation" gap={ 1 } columnCount={ 1 }> <AutoGrid columnCount={ 1 } gap={ 1 } id="nitro-catalog-main-navigation">
{ searchResult && (searchResult.filteredNodes.length > 0) && searchResult.filteredNodes.map((n, index) => { searchResult && (searchResult.filteredNodes.length > 0) && searchResult.filteredNodes.map((n, index) =>
{ {
return <CatalogNavigationItemView key={ index } node={ n } />; return <CatalogNavigationItemView key={ index } node={ n } />;

View File

@ -51,9 +51,9 @@ export const CatalogGridOfferView: FC<CatalogGridOfferViewProps> = props =>
if(!product) return null; if(!product) return null;
return ( return (
<LayoutGridItem itemImage={ iconUrl } itemCount={ ((offer.pricingModel === Offer.PRICING_MODEL_MULTI) ? product.productCount : 1) } itemUniqueSoldout={ (product.uniqueLimitedItemSeriesSize && !product.uniqueLimitedItemsLeft) } itemUniqueNumber={ product.uniqueLimitedItemSeriesSize } itemActive={ itemActive } onMouseDown={ onMouseEvent } onMouseUp={ onMouseEvent } onMouseOut={ onMouseEvent } { ...rest }> <LayoutGridItem itemActive={ itemActive } itemCount={ ((offer.pricingModel === Offer.PRICING_MODEL_MULTI) ? product.productCount : 1) } itemImage={ iconUrl } itemUniqueNumber={ product.uniqueLimitedItemSeriesSize } itemUniqueSoldout={ (product.uniqueLimitedItemSeriesSize && !product.uniqueLimitedItemsLeft) } onMouseDown={ onMouseEvent } onMouseOut={ onMouseEvent } onMouseUp={ onMouseEvent } { ...rest }>
{ (offer.product.productType === ProductTypeEnum.ROBOT) && { (offer.product.productType === ProductTypeEnum.ROBOT) &&
<LayoutAvatarImageView figure={ offer.product.extraParam } headOnly={ true } direction={ 3 } /> } <LayoutAvatarImageView direction={ 3 } figure={ offer.product.extraParam } headOnly={ true } /> }
</LayoutGridItem> </LayoutGridItem>
); );
} }

View File

@ -2,7 +2,7 @@ import { RedeemVoucherMessageComposer, VoucherRedeemErrorMessageEvent, VoucherRe
import { FC, useState } from 'react'; import { FC, useState } from 'react';
import { FaTag } from 'react-icons/fa'; import { FaTag } from 'react-icons/fa';
import { LocalizeText, SendMessageComposer } from '../../../../../api'; import { LocalizeText, SendMessageComposer } from '../../../../../api';
import { Button, Flex } from '../../../../../common'; import { Button } from '../../../../../common';
import { useMessageEvent, useNotification } from '../../../../../hooks'; import { useMessageEvent, useNotification } from '../../../../../hooks';
export interface CatalogRedeemVoucherViewProps export interface CatalogRedeemVoucherViewProps
@ -50,11 +50,11 @@ export const CatalogRedeemVoucherView: FC<CatalogRedeemVoucherViewProps> = props
}); });
return ( return (
<Flex gap={ 1 }> <div className="flex gap-1">
<input type="text" className="form-control form-control-sm" placeholder={ text } value={ voucher } onChange={ event => setVoucher(event.target.value) } /> <input className="form-control form-control-sm" placeholder={ text } type="text" value={ voucher } onChange={ event => setVoucher(event.target.value) } />
<Button variant="primary" onClick={ redeemVoucher } disabled={ isWaiting }> <Button disabled={ isWaiting } variant="primary" onClick={ redeemVoucher }>
<FaTag className="fa-icon" /> <FaTag className="fa-icon" />
</Button> </Button>
</Flex> </div>
); );
} }

View File

@ -77,18 +77,18 @@ export const CatalogSearchView: FC<{}> = props =>
}, [ offersToNodes, currentType, rootNode, searchValue, setCurrentPage, setSearchResult ]); }, [ offersToNodes, currentType, rootNode, searchValue, setCurrentPage, setSearchResult ]);
return ( return (
<Flex gap={ 1 }> <div className="flex gap-1">
<Flex fullWidth alignItems="center" position="relative"> <Flex fullWidth alignItems="center" position="relative">
<input type="text" className="form-control form-control-sm" placeholder={ LocalizeText('generic.search') } value={ searchValue } onChange={ event => setSearchValue(event.target.value) } /> <input className="form-control form-control-sm" placeholder={ LocalizeText('generic.search') } type="text" value={ searchValue } onChange={ event => setSearchValue(event.target.value) } />
</Flex> </Flex>
{ (!searchValue || !searchValue.length) && { (!searchValue || !searchValue.length) &&
<Button variant="primary" className="catalog-search-button"> <Button className="catalog-search-button" variant="primary">
<FaSearch className="fa-icon" /> <FaSearch className="fa-icon" />
</Button> } </Button> }
{ searchValue && !!searchValue.length && { searchValue && !!searchValue.length &&
<Button variant="primary" className="catalog-search-button" onClick={ event => setSearchValue('') }> <Button className="catalog-search-button" variant="primary" onClick={ event => setSearchValue('') }>
<FaTimes className="fa-icon" /> <FaTimes className="fa-icon" />
</Button> } </Button> }
</Flex> </div>
); );
} }

View File

@ -1,6 +1,6 @@
import { FC } from 'react'; import { FC } from 'react';
import { LocalizeText } from '../../../../../api'; import { LocalizeText } from '../../../../../api';
import { Base, Column, Flex, Grid, Text } from '../../../../../common'; import { Column, Grid, Text } from '../../../../../common';
import { useCatalog } from '../../../../../hooks'; import { useCatalog } from '../../../../../hooks';
import { CatalogBadgeSelectorWidgetView } from '../widgets/CatalogBadgeSelectorWidgetView'; import { CatalogBadgeSelectorWidgetView } from '../widgets/CatalogBadgeSelectorWidgetView';
import { CatalogFirstProductSelectorWidgetView } from '../widgets/CatalogFirstProductSelectorWidgetView'; import { CatalogFirstProductSelectorWidgetView } from '../widgets/CatalogFirstProductSelectorWidgetView';
@ -20,14 +20,14 @@ export const CatalogLayoutBadgeDisplayView: FC<CatalogLayoutProps> = props =>
<> <>
<CatalogFirstProductSelectorWidgetView /> <CatalogFirstProductSelectorWidgetView />
<Grid> <Grid>
<Column size={ 7 } overflow="hidden"> <Column overflow="hidden" size={ 7 }>
<CatalogItemGridWidgetView shrink /> <CatalogItemGridWidgetView shrink />
<Column gap={ 1 } overflow="hidden"> <Column gap={ 1 } overflow="hidden">
<Text truncate shrink fontWeight="bold">{ LocalizeText('catalog_selectbadge') }</Text> <Text shrink truncate fontWeight="bold">{ LocalizeText('catalog_selectbadge') }</Text>
<CatalogBadgeSelectorWidgetView /> <CatalogBadgeSelectorWidgetView />
</Column> </Column>
</Column> </Column>
<Column center={ !currentOffer } size={ 5 } overflow="hidden"> <Column center={ !currentOffer } overflow="hidden" size={ 5 }>
{ !currentOffer && { !currentOffer &&
<> <>
{ !!page.localization.getImage(1) && <img alt="" src={ page.localization.getImage(1) } /> } { !!page.localization.getImage(1) && <img alt="" src={ page.localization.getImage(1) } /> }
@ -35,15 +35,15 @@ export const CatalogLayoutBadgeDisplayView: FC<CatalogLayoutProps> = props =>
</> } </> }
{ currentOffer && { currentOffer &&
<> <>
<Base position="relative" overflow="hidden"> <div className="position-relative overflow-hidden">
<CatalogViewProductWidgetView /> <CatalogViewProductWidgetView />
</Base> </div>
<Column grow gap={ 1 }> <Column className="flex-grow-1" gap={ 1 }>
<CatalogLimitedItemWidgetView fullWidth /> <CatalogLimitedItemWidgetView />
<Text grow truncate>{ currentOffer.localizationName }</Text> <Text truncate className="flex-grow-1">{ currentOffer.localizationName }</Text>
<Flex justifyContent="end"> <div className="flex justify-content-end">
<CatalogTotalPriceWidget alignItems="end" /> <CatalogTotalPriceWidget alignItems="end" />
</Flex> </div>
<CatalogPurchaseWidgetView /> <CatalogPurchaseWidgetView />
</Column> </Column>
</> } </> }

View File

@ -2,7 +2,7 @@ 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 { FaFillDrip } from 'react-icons/fa';
import { IPurchasableOffer } from '../../../../../api'; import { IPurchasableOffer } from '../../../../../api';
import { AutoGrid, Base, Button, Column, Flex, Grid, LayoutGridItem, Text } from '../../../../../common'; import { AutoGrid, Button, Column, Grid, LayoutGridItem, Text } from '../../../../../common';
import { useCatalog } from '../../../../../hooks'; import { useCatalog } from '../../../../../hooks';
import { CatalogGridOfferView } from '../common/CatalogGridOfferView'; import { CatalogGridOfferView } from '../common/CatalogGridOfferView';
import { CatalogAddOnBadgeWidgetView } from '../widgets/CatalogAddOnBadgeWidgetView'; import { CatalogAddOnBadgeWidgetView } from '../widgets/CatalogAddOnBadgeWidgetView';
@ -132,17 +132,17 @@ export const CatalogLayoutColorGroupingView : FC<CatalogLayoutColorGroupViewProp
return ( return (
<Grid> <Grid>
<Column size={ 7 } overflow="hidden"> <Column overflow="hidden" size={ 7 }>
<AutoGrid columnCount={ 5 }> <AutoGrid columnCount={ 5 }>
{ (!colorsShowing || !currentOffer || !colorableItems.has(currentOffer.product.furnitureData.className)) && { (!colorsShowing || !currentOffer || !colorableItems.has(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 }/>) 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 }/>)
} }
{ (colorsShowing && currentOffer && colorableItems.has(currentOffer.product.furnitureData.className)) && { (colorsShowing && currentOffer && colorableItems.has(currentOffer.product.furnitureData.className)) &&
colorableItems.get(currentOffer.product.furnitureData.className).map((color, index) => <LayoutGridItem itemHighlight key={ index } itemActive={ (currentOffer.product.furnitureData.colorIndex === index) } itemColor={ ColorConverter.int2rgb(color) } className="clear-bg" onClick={ event => selectColor(index, currentOffer.product.furnitureData.className) } />) 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) } />)
} }
</AutoGrid> </AutoGrid>
</Column> </Column>
<Column center={ !currentOffer } size={ 5 } overflow="hidden"> <Column center={ !currentOffer } overflow="hidden" size={ 5 }>
{ !currentOffer && { !currentOffer &&
<> <>
{ !!page.localization.getImage(1) && <img alt="" src={ page.localization.getImage(1) } /> } { !!page.localization.getImage(1) && <img alt="" src={ page.localization.getImage(1) } /> }
@ -150,23 +150,23 @@ export const CatalogLayoutColorGroupingView : FC<CatalogLayoutColorGroupViewProp
</> } </> }
{ currentOffer && { currentOffer &&
<> <>
<Base position="relative" overflow="hidden"> <div className="position-relative overflow-hidden">
<CatalogViewProductWidgetView /> <CatalogViewProductWidgetView />
<CatalogAddOnBadgeWidgetView position="absolute" className="bg-muted rounded bottom-1 end-1" /> <CatalogAddOnBadgeWidgetView className="bg-muted rounded bottom-1 end-1" position="absolute" />
{ currentOffer.product.furnitureData.hasIndexedColor && { currentOffer.product.furnitureData.hasIndexedColor &&
<Button position="absolute" className="bottom-1 start-1" onClick={ event =>setColorsShowing(prev => !prev) }> <Button className="bottom-1 start-1" position="absolute" onClick={ event =>setColorsShowing(prev => !prev) }>
<FaFillDrip className="fa-icon" /> <FaFillDrip className="fa-icon" />
</Button> } </Button> }
</Base> </div>
<Column grow gap={ 1 }> <Column className="flex-grow-1" gap={ 1 }>
<CatalogLimitedItemWidgetView fullWidth /> <CatalogLimitedItemWidgetView />
<Text grow truncate>{ currentOffer.localizationName }</Text> <Text truncate className="flex-grow-1">{ currentOffer.localizationName }</Text>
<Flex justifyContent="between"> <div className="flex justify-content-between">
<Column gap={ 1 }> <div className="flex flex-column gap-1">
<CatalogSpinnerWidgetView /> <CatalogSpinnerWidgetView />
</Column> </div>
<CatalogTotalPriceWidget justifyContent="end" alignItems="end" /> <CatalogTotalPriceWidget alignItems="end" justifyContent="end" />
</Flex> </div>
<CatalogPurchaseWidgetView /> <CatalogPurchaseWidgetView />
</Column> </Column>
</> } </> }

View File

@ -20,12 +20,12 @@ export const CatalogLayoutDefaultView: FC<CatalogLayoutProps> = props =>
return ( return (
<> <>
<Grid> <Grid>
<Column size={ 7 } overflow="hidden"> <Column overflow="hidden" size={ 7 }>
{ GetConfigurationValue('catalog.headers') && { GetConfigurationValue('catalog.headers') &&
<CatalogHeaderView imageUrl={ currentPage.localization.getImage(0) }/> } <CatalogHeaderView imageUrl={ currentPage.localization.getImage(0) }/> }
<CatalogItemGridWidgetView /> <CatalogItemGridWidgetView />
</Column> </Column>
<Column center={ !currentOffer } size={ 5 } overflow="hidden"> <Column center={ !currentOffer } overflow="hidden" size={ 5 }>
{ !currentOffer && { !currentOffer &&
<> <>
{ !!page.localization.getImage(1) && { !!page.localization.getImage(1) &&
@ -43,14 +43,14 @@ export const CatalogLayoutDefaultView: FC<CatalogLayoutProps> = props =>
{ (currentOffer.product.productType === ProductTypeEnum.BADGE) && <CatalogAddOnBadgeWidgetView className="scale-2" /> } { (currentOffer.product.productType === ProductTypeEnum.BADGE) && <CatalogAddOnBadgeWidgetView className="scale-2" /> }
</Flex> </Flex>
<Column grow gap={ 1 }> <Column grow gap={ 1 }>
<CatalogLimitedItemWidgetView fullWidth /> <CatalogLimitedItemWidgetView />
<Text grow truncate>{ currentOffer.localizationName }</Text> <Text grow truncate>{ currentOffer.localizationName }</Text>
<Flex justifyContent="between"> <div className="flex justify-content-between">
<Column gap={ 1 }> <div className="flex flex-column gap-1">
<CatalogSpinnerWidgetView /> <CatalogSpinnerWidgetView />
</Column> </div>
<CatalogTotalPriceWidget justifyContent="end" alignItems="end" /> <CatalogTotalPriceWidget alignItems="end" justifyContent="end" />
</Flex> </div>
<CatalogPurchaseWidgetView /> <CatalogPurchaseWidgetView />
</Column> </Column>
</> } </> }

View File

@ -1,5 +1,5 @@
import { FC } from 'react'; import { FC } from 'react';
import { Base, Column, Flex, Grid, Text } from '../../../../../common'; import { Column, Grid, Text } from '../../../../../common';
import { useCatalog } from '../../../../../hooks'; import { useCatalog } from '../../../../../hooks';
import { CatalogGuildBadgeWidgetView } from '../widgets/CatalogGuildBadgeWidgetView'; import { CatalogGuildBadgeWidgetView } from '../widgets/CatalogGuildBadgeWidgetView';
import { CatalogGuildSelectorWidgetView } from '../widgets/CatalogGuildSelectorWidgetView'; import { CatalogGuildSelectorWidgetView } from '../widgets/CatalogGuildSelectorWidgetView';
@ -16,10 +16,10 @@ export const CatalogLayouGuildCustomFurniView: FC<CatalogLayoutProps> = props =>
return ( return (
<Grid> <Grid>
<Column size={ 7 } overflow="hidden"> <Column overflow="hidden" size={ 7 }>
<CatalogItemGridWidgetView /> <CatalogItemGridWidgetView />
</Column> </Column>
<Column center={ !currentOffer } size={ 5 } overflow="hidden"> <Column center={ !currentOffer } overflow="hidden" size={ 5 }>
{ !currentOffer && { !currentOffer &&
<> <>
{ !!page.localization.getImage(1) && <img alt="" src={ page.localization.getImage(1) } /> } { !!page.localization.getImage(1) && <img alt="" src={ page.localization.getImage(1) } /> }
@ -27,18 +27,18 @@ export const CatalogLayouGuildCustomFurniView: FC<CatalogLayoutProps> = props =>
</> } </> }
{ currentOffer && { currentOffer &&
<> <>
<Base position="relative" overflow="hidden"> <div className="position-relative overflow-hidden">
<CatalogViewProductWidgetView /> <CatalogViewProductWidgetView />
<CatalogGuildBadgeWidgetView position="absolute" className="bottom-1 end-1" /> <CatalogGuildBadgeWidgetView className="bottom-1 end-1" position="absolute" />
</Base> </div>
<Column grow gap={ 1 }> <Column grow gap={ 1 }>
<Text truncate>{ currentOffer.localizationName }</Text> <Text truncate>{ currentOffer.localizationName }</Text>
<Base grow> <div className="flex-grow-1">
<CatalogGuildSelectorWidgetView /> <CatalogGuildSelectorWidgetView />
</Base> </div>
<Flex justifyContent="end"> <div className="flex justify-content-end">
<CatalogTotalPriceWidget alignItems="end" /> <CatalogTotalPriceWidget alignItems="end" />
</Flex> </div>
<CatalogPurchaseWidgetView /> <CatalogPurchaseWidgetView />
</Column> </Column>
</> } </> }

View File

@ -1,7 +1,7 @@
import { CatalogGroupsComposer } from '@nitrots/nitro-renderer'; import { CatalogGroupsComposer } from '@nitrots/nitro-renderer';
import { FC, useEffect, useState } from 'react'; import { FC, useEffect, useState } from 'react';
import { SendMessageComposer } from '../../../../../api'; import { SendMessageComposer } from '../../../../../api';
import { Base, Column, Flex, Grid, Text } from '../../../../../common'; import { Column, Grid, Text } from '../../../../../common';
import { useCatalog } from '../../../../../hooks'; import { useCatalog } from '../../../../../hooks';
import { CatalogFirstProductSelectorWidgetView } from '../widgets/CatalogFirstProductSelectorWidgetView'; import { CatalogFirstProductSelectorWidgetView } from '../widgets/CatalogFirstProductSelectorWidgetView';
import { CatalogGuildSelectorWidgetView } from '../widgets/CatalogGuildSelectorWidgetView'; import { CatalogGuildSelectorWidgetView } from '../widgets/CatalogGuildSelectorWidgetView';
@ -25,20 +25,20 @@ export const CatalogLayouGuildForumView: FC<CatalogLayoutProps> = props =>
<> <>
<CatalogFirstProductSelectorWidgetView /> <CatalogFirstProductSelectorWidgetView />
<Grid> <Grid>
<Column className="bg-muted rounded p-2 text-black" size={ 7 } overflow="hidden"> <Column className="bg-muted rounded p-2 text-black" overflow="hidden" size={ 7 }>
<Base className="overflow-auto" dangerouslySetInnerHTML={ { __html: page.localization.getText(1) } } /> <div className="overflow-auto" dangerouslySetInnerHTML={ { __html: page.localization.getText(1) } } />
</Column> </Column>
<Column size={ 5 } overflow="hidden" gap={ 1 }> <Column gap={ 1 } overflow="hidden" size={ 5 }>
{ !!currentOffer && { !!currentOffer &&
<> <>
<Column grow gap={ 1 }> <Column grow gap={ 1 }>
<Text truncate>{ currentOffer.localizationName }</Text> <Text truncate>{ currentOffer.localizationName }</Text>
<Base grow> <div className="flex-grow-1">
<CatalogGuildSelectorWidgetView /> <CatalogGuildSelectorWidgetView />
</Base> </div>
<Flex justifyContent="end"> <div className="flex justify-content-end">
<CatalogTotalPriceWidget alignItems="end" /> <CatalogTotalPriceWidget alignItems="end" />
</Flex> </div>
<CatalogPurchaseWidgetView noGiftOption={ true } /> <CatalogPurchaseWidgetView noGiftOption={ true } />
</Column> </Column>
</> } </> }

View File

@ -1,7 +1,6 @@
import { CreateLinkEvent } from '@nitrots/nitro-renderer'; import { CreateLinkEvent } from '@nitrots/nitro-renderer';
import { FC } from 'react'; import { FC } from 'react';
import { LocalizeText } from '../../../../../api'; import { LocalizeText } from '../../../../../api';
import { Base } from '../../../../../common/Base';
import { Button } from '../../../../../common/Button'; import { Button } from '../../../../../common/Button';
import { Column } from '../../../../../common/Column'; import { Column } from '../../../../../common/Column';
import { Grid } from '../../../../../common/Grid'; import { Grid } from '../../../../../common/Grid';
@ -14,12 +13,12 @@ export const CatalogLayouGuildFrontpageView: FC<CatalogLayoutProps> = props =>
return ( return (
<Grid> <Grid>
<Column size={ 7 } overflow="hidden" className="bg-muted rounded p-2 text-black"> <Column className="bg-muted rounded p-2 text-black" overflow="hidden" size={ 7 }>
<Base dangerouslySetInnerHTML={ { __html: page.localization.getText(2) } } /> <div dangerouslySetInnerHTML={ { __html: page.localization.getText(2) } } />
<Base overflow="auto" dangerouslySetInnerHTML={ { __html: page.localization.getText(0) } } /> <div className="overflow-auto" dangerouslySetInnerHTML={ { __html: page.localization.getText(0) } } />
<Base dangerouslySetInnerHTML={ { __html: page.localization.getText(1) } } /> <div dangerouslySetInnerHTML={ { __html: page.localization.getText(1) } } />
</Column> </Column>
<Column center size={ 5 } overflow="hidden"> <Column center overflow="hidden" size={ 5 }>
<LayoutImage imageUrl={ page.localization.getImage(1) } /> <LayoutImage imageUrl={ page.localization.getImage(1) } />
<Button onClick={ () => CreateLinkEvent('groups/create') }> <Button onClick={ () => CreateLinkEvent('groups/create') }>
{ LocalizeText('catalog.start.guild.purchase.button') } { LocalizeText('catalog.start.guild.purchase.button') }

View File

@ -6,8 +6,8 @@ export const CatalogLayoutInfoLoyaltyView: FC<CatalogLayoutProps> = props =>
const { page = null } = props; const { page = null } = props;
return ( return (
<div className="h-100 nitro-catalog-layout-info-loyalty text-black d-flex flex-row"> <div className="h-100 nitro-catalog-layout-info-loyalty text-black flex flex-row">
<div className="overflow-auto h-100 d-flex flex-column info-loyalty-content"> <div className="overflow-auto h-100 flex flex-column info-loyalty-content">
<div dangerouslySetInnerHTML={ { __html: page.localization.getText(0) } } /> <div dangerouslySetInnerHTML={ { __html: page.localization.getText(0) } } />
</div> </div>
</div> </div>

View File

@ -1,5 +1,5 @@
import { FC } from 'react'; import { FC } from 'react';
import { Base, Column, Flex } from '../../../../../common'; import { Column } from '../../../../../common';
import { CatalogLayoutProps } from './CatalogLayout.types'; import { CatalogLayoutProps } from './CatalogLayout.types';
export const CatalogLayoutPets3View: FC<CatalogLayoutProps> = props => export const CatalogLayoutPets3View: FC<CatalogLayoutProps> = props =>
@ -10,16 +10,16 @@ export const CatalogLayoutPets3View: FC<CatalogLayoutProps> = props =>
return ( return (
<Column grow className="bg-muted rounded text-black p-2" overflow="hidden"> <Column grow className="bg-muted rounded text-black p-2" overflow="hidden">
<Flex alignItems="center" gap={ 2 }> <div className="items-center gap-2">
{ imageUrl && <img alt="" src={ imageUrl } /> } { imageUrl && <img alt="" src={ imageUrl } /> }
<Base className="fs-5" dangerouslySetInnerHTML={ { __html: page.localization.getText(1) } } /> <div className="fs-5" dangerouslySetInnerHTML={ { __html: page.localization.getText(1) } } />
</Flex> </div>
<Column grow alignItems="center" overflow="auto"> <Column grow alignItems="center" overflow="auto">
<Base dangerouslySetInnerHTML={ { __html: page.localization.getText(2) } } /> <div dangerouslySetInnerHTML={ { __html: page.localization.getText(2) } } />
</Column> </Column>
<Flex alignItems="center"> <div className="flex items-center">
<Base className="fw-bold" dangerouslySetInnerHTML={ { __html: page.localization.getText(3) } } /> <div className="fw-bold" dangerouslySetInnerHTML={ { __html: page.localization.getText(3) } } />
</Flex> </div>
</Column> </Column>
); );
} }

View File

@ -1,7 +1,7 @@
import { GetRoomAdPurchaseInfoComposer, GetUserEventCatsMessageComposer, PurchaseRoomAdMessageComposer, RoomAdPurchaseInfoEvent, RoomEntryData } from '@nitrots/nitro-renderer'; import { GetRoomAdPurchaseInfoComposer, GetUserEventCatsMessageComposer, PurchaseRoomAdMessageComposer, RoomAdPurchaseInfoEvent, RoomEntryData } from '@nitrots/nitro-renderer';
import { FC, useEffect, useState } from 'react'; import { FC, useEffect, useState } from 'react';
import { LocalizeText, SendMessageComposer } from '../../../../../api'; import { LocalizeText, SendMessageComposer } from '../../../../../api';
import { Base, Button, Column, Text } from '../../../../../common'; import { Button, Column, Text } from '../../../../../common';
import { useCatalog, useMessageEvent, useNavigator, useRoomPromote } from '../../../../../hooks'; import { useCatalog, useMessageEvent, useNavigator, useRoomPromote } from '../../../../../hooks';
import { CatalogLayoutProps } from './CatalogLayout.types'; import { CatalogLayoutProps } from './CatalogLayout.types';
@ -73,34 +73,34 @@ export const CatalogLayoutRoomAdsView: FC<CatalogLayoutProps> = props =>
return (<> return (<>
<Text bold center>{ LocalizeText('roomad.catalog_header') }</Text> <Text bold center>{ LocalizeText('roomad.catalog_header') }</Text>
<Column size={ 12 } overflow="hidden" className="text-black"> <Column className="text-black" overflow="hidden" size={ 12 }>
<Base>{ LocalizeText('roomad.catalog_text', [ 'duration' ], [ '120' ]) }</Base> <div>{ LocalizeText('roomad.catalog_text', [ 'duration' ], [ '120' ]) }</div>
<Base className="bg-muted rounded p-1"> <div className="bg-muted rounded p-1">
<Column gap={ 2 }> <Column gap={ 2 }>
<Text bold>{ LocalizeText('navigator.category') }</Text> <Text bold>{ LocalizeText('navigator.category') }</Text>
<select className="form-select form-select-sm" value={ categoryId } onChange={ event => setCategoryId(parseInt(event.target.value)) } disabled={ extended }> <select className="form-select form-select-sm" disabled={ extended } value={ categoryId } onChange={ event => setCategoryId(parseInt(event.target.value)) }>
{ categories && categories.map((cat, index) => <option key={ index } value={ cat.id }>{ LocalizeText(cat.name) }</option>) } { categories && categories.map((cat, index) => <option key={ index } value={ cat.id }>{ LocalizeText(cat.name) }</option>) }
</select> </select>
</Column> </Column>
<Column gap={ 1 }> <div className="flex flex-column gap-1">
<Text bold>{ LocalizeText('roomad.catalog_name') }</Text> <Text bold>{ LocalizeText('roomad.catalog_name') }</Text>
<input type="text" className="form-control form-control-sm" maxLength={ 64 } value={ eventName } onChange={ event => setEventName(event.target.value) } readOnly={ extended } /> <input className="form-control form-control-sm" maxLength={ 64 } readOnly={ extended } type="text" value={ eventName } onChange={ event => setEventName(event.target.value) } />
</Column> </div>
<Column gap={ 1 }> <div className="flex flex-column gap-1">
<Text bold>{ LocalizeText('roomad.catalog_description') }</Text> <Text bold>{ LocalizeText('roomad.catalog_description') }</Text>
<textarea className="form-control form-control-sm" maxLength={ 64 } value={ eventDesc } onChange={ event => setEventDesc(event.target.value) } readOnly={ extended } /> <textarea className="form-control form-control-sm" maxLength={ 64 } readOnly={ extended } value={ eventDesc } onChange={ event => setEventDesc(event.target.value) } />
</Column> </div>
<Column gap={ 1 }> <div className="flex flex-column gap-1">
<Text bold>{ LocalizeText('roomad.catalog_roomname') }</Text> <Text bold>{ LocalizeText('roomad.catalog_roomname') }</Text>
<select className="form-select form-select-sm" value={ roomId } onChange={ event => setRoomId(parseInt(event.target.value)) } disabled={ extended }> <select className="form-select form-select-sm" disabled={ extended } value={ roomId } onChange={ event => setRoomId(parseInt(event.target.value)) }>
<option value={ -1 } disabled>{ LocalizeText('roomad.catalog_roomname') }</option> <option disabled value={ -1 }>{ LocalizeText('roomad.catalog_roomname') }</option>
{ availableRooms && availableRooms.map((room, index) => <option key={ index } value={ room.roomId }>{ room.roomName }</option>) } { availableRooms && availableRooms.map((room, index) => <option key={ index } value={ room.roomId }>{ room.roomName }</option>) }
</select> </select>
</Column> </div>
<Column gap={ 1 }> <div className="flex flex-column gap-1">
<Button variant={ (!eventName || !eventDesc || roomId === -1) ? 'danger' : 'success' } onClick={ purchaseAd } disabled={ (!eventName || !eventDesc || roomId === -1) }>{ extended ? LocalizeText('roomad.extend.event') : LocalizeText('buy') }</Button> <Button disabled={ (!eventName || !eventDesc || roomId === -1) } variant={ (!eventName || !eventDesc || roomId === -1) ? 'danger' : 'success' } onClick={ purchaseAd }>{ extended ? LocalizeText('roomad.extend.event') : LocalizeText('buy') }</Button>
</Column> </div>
</Base> </div>
</Column> </Column>
</> </>
); );

View File

@ -15,25 +15,25 @@ export const CatalogLayoutRoomBundleView: FC<CatalogLayoutProps> = props =>
<> <>
<CatalogFirstProductSelectorWidgetView /> <CatalogFirstProductSelectorWidgetView />
<Grid> <Grid>
<Column size={ 7 } overflow="hidden"> <Column overflow="hidden" size={ 7 }>
{ !!page.localization.getText(2) && { !!page.localization.getText(2) &&
<Text dangerouslySetInnerHTML={ { __html: page.localization.getText(2) } } /> } <Text dangerouslySetInnerHTML={ { __html: page.localization.getText(2) } } /> }
<Column grow overflow="hidden" className="bg-muted p-2 rounded"> <Column grow className="bg-muted p-2 rounded" overflow="hidden">
<CatalogBundleGridWidgetView fullWidth className="nitro-catalog-layout-bundle-grid" /> <CatalogBundleGridWidgetView fullWidth className="nitro-catalog-layout-bundle-grid" />
</Column> </Column>
</Column> </Column>
<Column size={ 5 } overflow="hidden" gap={ 1 }> <Column gap={ 1 } overflow="hidden" size={ 5 }>
{ !!page.localization.getText(1) && { !!page.localization.getText(1) &&
<Text center small overflow="auto">{ page.localization.getText(1) }</Text> } <Text center small overflow="auto">{ page.localization.getText(1) }</Text> }
<Column grow position="relative" overflow="hidden" gap={ 0 }> <Column grow gap={ 0 } overflow="hidden" position="relative">
{ !!page.localization.getImage(1) && { !!page.localization.getImage(1) &&
<img alt="" className="flex-grow-1" src={ page.localization.getImage(1) } /> } <img alt="" className="flex-grow-1" src={ page.localization.getImage(1) } /> }
<CatalogAddOnBadgeWidgetView position="absolute" className="bg-muted rounded bottom-0 start-0" /> <CatalogAddOnBadgeWidgetView className="bg-muted rounded bottom-0 start-0" position="absolute" />
<CatalogSimplePriceWidgetView position="absolute" className="bottom-0 end-0" /> <CatalogSimplePriceWidgetView />
</Column> </Column>
<Column gap={ 1 }> <div className="flex flex-column gap-1">
<CatalogPurchaseWidgetView /> <CatalogPurchaseWidgetView />
</Column> </div>
</Column> </Column>
</Grid> </Grid>
</> </>

View File

@ -15,25 +15,25 @@ export const CatalogLayoutSingleBundleView: FC<CatalogLayoutProps> = props =>
<> <>
<CatalogFirstProductSelectorWidgetView /> <CatalogFirstProductSelectorWidgetView />
<Grid> <Grid>
<Column size={ 7 } overflow="hidden"> <Column overflow="hidden" size={ 7 }>
{ !!page.localization.getText(2) && { !!page.localization.getText(2) &&
<Text dangerouslySetInnerHTML={ { __html: page.localization.getText(2) } } /> } <Text dangerouslySetInnerHTML={ { __html: page.localization.getText(2) } } /> }
<Column grow overflow="hidden" className="bg-muted p-2 rounded"> <Column grow className="bg-muted p-2 rounded" overflow="hidden">
<CatalogBundleGridWidgetView fullWidth className="nitro-catalog-layout-bundle-grid" /> <CatalogBundleGridWidgetView fullWidth className="nitro-catalog-layout-bundle-grid" />
</Column> </Column>
</Column> </Column>
<Column size={ 5 } overflow="hidden" gap={ 1 }> <Column gap={ 1 } overflow="hidden" size={ 5 }>
{ !!page.localization.getText(1) && { !!page.localization.getText(1) &&
<Text center small overflow="auto">{ page.localization.getText(1) }</Text> } <Text center small overflow="auto">{ page.localization.getText(1) }</Text> }
<Column grow position="relative" overflow="hidden" gap={ 0 }> <Column grow gap={ 0 } overflow="hidden" position="relative">
{ !!page.localization.getImage(1) && { !!page.localization.getImage(1) &&
<img alt="" className="flex-grow-1" src={ page.localization.getImage(1) } /> } <img alt="" className="flex-grow-1" src={ page.localization.getImage(1) } /> }
<CatalogAddOnBadgeWidgetView position="absolute" className="bg-muted rounded bottom-0 start-0" /> <CatalogAddOnBadgeWidgetView className="bg-muted rounded bottom-0 start-0" position="absolute" />
<CatalogSimplePriceWidgetView position="absolute" className="bottom-0 end-0" /> <CatalogSimplePriceWidgetView />
</Column> </Column>
<Column gap={ 1 }> <div className="flex flex-column gap-1">
<CatalogPurchaseWidgetView /> <CatalogPurchaseWidgetView />
</Column> </div>
</Column> </Column>
</Grid> </Grid>
</> </>

View File

@ -1,7 +1,7 @@
import { GetOfficialSongIdMessageComposer, GetSoundManager, MusicPriorities, OfficialSongIdMessageEvent } from '@nitrots/nitro-renderer'; import { GetOfficialSongIdMessageComposer, GetSoundManager, MusicPriorities, OfficialSongIdMessageEvent } from '@nitrots/nitro-renderer';
import { FC, useEffect, useState } from 'react'; import { FC, useEffect, useState } from 'react';
import { GetConfigurationValue, LocalizeText, ProductTypeEnum, SendMessageComposer } from '../../../../../api'; import { GetConfigurationValue, LocalizeText, ProductTypeEnum, SendMessageComposer } from '../../../../../api';
import { Button, Column, Flex, Grid, LayoutImage, Text } from '../../../../../common'; import { Button, Column, Grid, LayoutImage, Text } from '../../../../../common';
import { useCatalog, useMessageEvent } from '../../../../../hooks'; import { useCatalog, useMessageEvent } from '../../../../../hooks';
import { CatalogHeaderView } from '../../catalog-header/CatalogHeaderView'; import { CatalogHeaderView } from '../../catalog-header/CatalogHeaderView';
import { CatalogAddOnBadgeWidgetView } from '../widgets/CatalogAddOnBadgeWidgetView'; import { CatalogAddOnBadgeWidgetView } from '../widgets/CatalogAddOnBadgeWidgetView';
@ -70,12 +70,12 @@ export const CatalogLayoutSoundMachineView: FC<CatalogLayoutProps> = props =>
return ( return (
<> <>
<Grid> <Grid>
<Column size={ 7 } overflow="hidden"> <Column overflow="hidden" size={ 7 }>
{ GetConfigurationValue('catalog.headers') && { GetConfigurationValue('catalog.headers') &&
<CatalogHeaderView imageUrl={ currentPage.localization.getImage(0) }/> } <CatalogHeaderView imageUrl={ currentPage.localization.getImage(0) }/> }
<CatalogItemGridWidgetView /> <CatalogItemGridWidgetView />
</Column> </Column>
<Column center={ !currentOffer } size={ 5 } overflow="hidden"> <Column center={ !currentOffer } overflow="hidden" size={ 5 }>
{ !currentOffer && { !currentOffer &&
<> <>
{ !!page.localization.getImage(1) && { !!page.localization.getImage(1) &&
@ -84,25 +84,25 @@ export const CatalogLayoutSoundMachineView: FC<CatalogLayoutProps> = props =>
</> } </> }
{ currentOffer && { currentOffer &&
<> <>
<Flex center overflow="hidden" style={ { height: 140 } }> <div className="flex items-center justify-center overflow-hidden" style={ { height: 140 } }>
{ (currentOffer.product.productType !== ProductTypeEnum.BADGE) && { (currentOffer.product.productType !== ProductTypeEnum.BADGE) &&
<> <>
<CatalogViewProductWidgetView /> <CatalogViewProductWidgetView />
<CatalogAddOnBadgeWidgetView className="bg-muted rounded bottom-1 end-1" /> <CatalogAddOnBadgeWidgetView className="bg-muted rounded bottom-1 end-1" />
</> } </> }
{ (currentOffer.product.productType === ProductTypeEnum.BADGE) && <CatalogAddOnBadgeWidgetView className="scale-2" /> } { (currentOffer.product.productType === ProductTypeEnum.BADGE) && <CatalogAddOnBadgeWidgetView className="scale-2" /> }
</Flex> </div>
<Column grow gap={ 1 }> <Column grow gap={ 1 }>
<CatalogLimitedItemWidgetView fullWidth /> <CatalogLimitedItemWidgetView />
<Text grow truncate>{ currentOffer.localizationName }</Text> <Text grow truncate>{ currentOffer.localizationName }</Text>
{ songId > -1 && <Button onClick={ () => previewSong(songId) }>{ LocalizeText('play_preview_button') }</Button> { songId > -1 && <Button onClick={ () => previewSong(songId) }>{ LocalizeText('play_preview_button') }</Button>
} }
<Flex justifyContent="between"> <div className="flex justify-content-between">
<Column gap={ 1 }> <div className="flex flex-column gap-1">
<CatalogSpinnerWidgetView /> <CatalogSpinnerWidgetView />
</Column> </div>
<CatalogTotalPriceWidget justifyContent="end" alignItems="end" /> <CatalogTotalPriceWidget alignItems="end" justifyContent="end" />
</Flex> </div>
<CatalogPurchaseWidgetView /> <CatalogPurchaseWidgetView />
</Column> </Column>
</> } </> }

View File

@ -1,5 +1,5 @@
import { FC, useEffect } from 'react'; import { FC, useEffect } from 'react';
import { Base, Column, Flex, Grid, Text } from '../../../../../common'; import { Column, Grid, Text } from '../../../../../common';
import { useCatalog } from '../../../../../hooks'; import { useCatalog } from '../../../../../hooks';
import { CatalogPurchaseWidgetView } from '../widgets/CatalogPurchaseWidgetView'; import { CatalogPurchaseWidgetView } from '../widgets/CatalogPurchaseWidgetView';
import { CatalogSpacesWidgetView } from '../widgets/CatalogSpacesWidgetView'; import { CatalogSpacesWidgetView } from '../widgets/CatalogSpacesWidgetView';
@ -19,10 +19,10 @@ export const CatalogLayoutSpacesView: FC<CatalogLayoutProps> = props =>
return ( return (
<Grid> <Grid>
<Column size={ 7 } overflow="hidden"> <Column overflow="hidden" size={ 7 }>
<CatalogSpacesWidgetView /> <CatalogSpacesWidgetView />
</Column> </Column>
<Column center={ !currentOffer } size={ 5 } overflow="hidden"> <Column center={ !currentOffer } overflow="hidden" size={ 5 }>
{ !currentOffer && { !currentOffer &&
<> <>
{ !!page.localization.getImage(1) && <img alt="" src={ page.localization.getImage(1) } /> } { !!page.localization.getImage(1) && <img alt="" src={ page.localization.getImage(1) } /> }
@ -30,14 +30,14 @@ export const CatalogLayoutSpacesView: FC<CatalogLayoutProps> = props =>
</> } </> }
{ currentOffer && { currentOffer &&
<> <>
<Base position="relative" overflow="hidden"> <div className="position-relative overflow-hidden">
<CatalogViewProductWidgetView /> <CatalogViewProductWidgetView />
</Base> </div>
<Column grow gap={ 1 }> <Column grow gap={ 1 }>
<Text grow truncate>{ currentOffer.localizationName }</Text> <Text grow truncate>{ currentOffer.localizationName }</Text>
<Flex justifyContent="end"> <div className="flex justify-content-end">
<CatalogTotalPriceWidget alignItems="end" /> <CatalogTotalPriceWidget alignItems="end" />
</Flex> </div>
<CatalogPurchaseWidgetView /> <CatalogPurchaseWidgetView />
</Column> </Column>
</> } </> }

View File

@ -1,5 +1,5 @@
import { FC, useEffect, useState } from 'react'; import { FC, useEffect, useState } from 'react';
import { Column, Flex, Grid, Text } from '../../../../../common'; import { Column, Grid, Text } from '../../../../../common';
import { useCatalog } from '../../../../../hooks'; import { useCatalog } from '../../../../../hooks';
import { CatalogItemGridWidgetView } from '../widgets/CatalogItemGridWidgetView'; import { CatalogItemGridWidgetView } from '../widgets/CatalogItemGridWidgetView';
import { CatalogPurchaseWidgetView } from '../widgets/CatalogPurchaseWidgetView'; import { CatalogPurchaseWidgetView } from '../widgets/CatalogPurchaseWidgetView';
@ -29,11 +29,11 @@ export const CatalogLayoutTrophiesView: FC<CatalogLayoutProps> = props =>
return ( return (
<Grid> <Grid>
<Column size={ 7 } overflow="hidden"> <Column overflow="hidden" size={ 7 }>
<CatalogItemGridWidgetView /> <CatalogItemGridWidgetView />
<textarea className="flex-grow-1 form-control w-100" defaultValue={ trophyText || '' } onChange={ event => setTrophyText(event.target.value) } /> <textarea className="flex-grow-1 form-control w-100" defaultValue={ trophyText || '' } onChange={ event => setTrophyText(event.target.value) } />
</Column> </Column>
<Column center={ !currentOffer } size={ 5 } overflow="hidden"> <Column center={ !currentOffer } overflow="hidden" size={ 5 }>
{ !currentOffer && { !currentOffer &&
<> <>
{ !!page.localization.getImage(1) && <img alt="" src={ page.localization.getImage(1) } /> } { !!page.localization.getImage(1) && <img alt="" src={ page.localization.getImage(1) } /> }
@ -44,9 +44,9 @@ export const CatalogLayoutTrophiesView: FC<CatalogLayoutProps> = props =>
<CatalogViewProductWidgetView /> <CatalogViewProductWidgetView />
<Column grow gap={ 1 }> <Column grow gap={ 1 }>
<Text grow truncate>{ currentOffer.localizationName }</Text> <Text grow truncate>{ currentOffer.localizationName }</Text>
<Flex justifyContent="end"> <div className="flex justify-content-end">
<CatalogTotalPriceWidget alignItems="end" /> <CatalogTotalPriceWidget alignItems="end" />
</Flex> </div>
<CatalogPurchaseWidgetView /> <CatalogPurchaseWidgetView />
</Column> </Column>
</> } </> }

View File

@ -2,7 +2,7 @@ import { ClubOfferData, GetClubOffersMessageComposer, PurchaseFromCatalogCompose
import { FC, useCallback, useEffect, useMemo, useState } from 'react'; import { FC, useCallback, useEffect, useMemo, useState } from 'react';
import { CatalogPurchaseState, LocalizeText, SendMessageComposer } from '../../../../../api'; import { CatalogPurchaseState, LocalizeText, SendMessageComposer } from '../../../../../api';
import { AutoGrid, Button, Column, Flex, Grid, LayoutCurrencyIcon, LayoutGridItem, LayoutLoadingSpinnerView, Text } from '../../../../../common'; import { AutoGrid, Button, Column, Flex, Grid, LayoutCurrencyIcon, LayoutGridItem, LayoutLoadingSpinnerView, Text } from '../../../../../common';
import { CatalogEvent, CatalogPurchasedEvent, CatalogPurchaseFailureEvent } from '../../../../../events'; import { CatalogEvent, CatalogPurchaseFailureEvent, CatalogPurchasedEvent } from '../../../../../events';
import { useCatalog, usePurse, useUiEvent } from '../../../../../hooks'; import { useCatalog, usePurse, useUiEvent } from '../../../../../hooks';
import { CatalogLayoutProps } from './CatalogLayout.types'; import { CatalogLayoutProps } from './CatalogLayout.types';
@ -114,9 +114,9 @@ export const CatalogLayoutVipBuyView: FC<CatalogLayoutProps> = props =>
case CatalogPurchaseState.CONFIRM: case CatalogPurchaseState.CONFIRM:
return <Button fullWidth variant="warning" onClick={ purchaseSubscription }>{ LocalizeText('catalog.marketplace.confirm_title') }</Button>; return <Button fullWidth variant="warning" onClick={ purchaseSubscription }>{ LocalizeText('catalog.marketplace.confirm_title') }</Button>;
case CatalogPurchaseState.PURCHASE: case CatalogPurchaseState.PURCHASE:
return <Button fullWidth variant="primary" disabled><LayoutLoadingSpinnerView /></Button>; return <Button disabled fullWidth variant="primary"><LayoutLoadingSpinnerView /></Button>;
case CatalogPurchaseState.FAILED: case CatalogPurchaseState.FAILED:
return <Button fullWidth variant="danger" disabled>{ LocalizeText('generic.failed') }</Button>; return <Button disabled fullWidth variant="danger">{ LocalizeText('generic.failed') }</Button>;
case CatalogPurchaseState.NONE: case CatalogPurchaseState.NONE:
default: default:
return <Button fullWidth variant="success" onClick={ () => setPurchaseState(CatalogPurchaseState.CONFIRM) }>{ LocalizeText('buy') }</Button>; return <Button fullWidth variant="success" onClick={ () => setPurchaseState(CatalogPurchaseState.CONFIRM) }>{ LocalizeText('buy') }</Button>;
@ -130,23 +130,23 @@ export const CatalogLayoutVipBuyView: FC<CatalogLayoutProps> = props =>
return ( return (
<Grid> <Grid>
<Column fullHeight size={ 7 } overflow="hidden" justifyContent="between"> <Column fullHeight justifyContent="between" overflow="hidden" size={ 7 }>
<AutoGrid columnCount={ 1 } className="nitro-catalog-layout-vip-buy-grid"> <AutoGrid className="nitro-catalog-layout-vip-buy-grid" columnCount={ 1 }>
{ clubOffers && (clubOffers.length > 0) && clubOffers.map((offer, index) => { clubOffers && (clubOffers.length > 0) && clubOffers.map((offer, index) =>
{ {
return ( return (
<LayoutGridItem key={ index } column={ false } center={ false } alignItems="center" justifyContent="between" itemActive={ pendingOffer === offer } className="p-1" onClick={ () => setOffer(offer) }> <LayoutGridItem key={ index } alignItems="center" center={ false } className="p-1" column={ false } itemActive={ pendingOffer === offer } justifyContent="between" onClick={ () => setOffer(offer) }>
<i className="icon-hc-banner" /> <i className="icon-hc-banner" />
<Column justifyContent="end" gap={ 0 }> <Column gap={ 0 } justifyContent="end">
<Text textEnd>{ getOfferText(offer) }</Text> <Text textEnd>{ getOfferText(offer) }</Text>
<Flex justifyContent="end" gap={ 1 }> <Flex gap={ 1 } justifyContent="end">
{ (offer.priceCredits > 0) && { (offer.priceCredits > 0) &&
<Flex alignItems="center" justifyContent="end" gap={ 1 }> <Flex alignItems="center" gap={ 1 } justifyContent="end">
<Text>{ offer.priceCredits }</Text> <Text>{ offer.priceCredits }</Text>
<LayoutCurrencyIcon type={ -1 } /> <LayoutCurrencyIcon type={ -1 } />
</Flex> } </Flex> }
{ (offer.priceActivityPoints > 0) && { (offer.priceActivityPoints > 0) &&
<Flex alignItems="center" justifyContent="end" gap={ 1 }> <Flex alignItems="center" gap={ 1 } justifyContent="end">
<Text>{ offer.priceActivityPoints }</Text> <Text>{ offer.priceActivityPoints }</Text>
<LayoutCurrencyIcon type={ offer.priceActivityPointsType } /> <LayoutCurrencyIcon type={ offer.priceActivityPointsType } />
</Flex> } </Flex> }
@ -158,10 +158,10 @@ export const CatalogLayoutVipBuyView: FC<CatalogLayoutProps> = props =>
</AutoGrid> </AutoGrid>
<Text center dangerouslySetInnerHTML={ { __html: LocalizeText('catalog.vip.buy.hccenter') } }></Text> <Text center dangerouslySetInnerHTML={ { __html: LocalizeText('catalog.vip.buy.hccenter') } }></Text>
</Column> </Column>
<Column size={ 5 } overflow="hidden"> <Column overflow="hidden" size={ 5 }>
<Column fullHeight center overflow="hidden"> <Column center fullHeight overflow="hidden">
{ currentPage.localization.getImage(1) && <img alt="" src={ currentPage.localization.getImage(1) } /> } { currentPage.localization.getImage(1) && <img alt="" src={ currentPage.localization.getImage(1) } /> }
<Text center overflow="auto" dangerouslySetInnerHTML={ { __html: getSubscriptionDetails } } /> <Text center dangerouslySetInnerHTML={ { __html: getSubscriptionDetails } } overflow="auto" />
</Column> </Column>
{ pendingOffer && { pendingOffer &&
<Column fullWidth grow justifyContent="end"> <Column fullWidth grow justifyContent="end">
@ -170,18 +170,18 @@ export const CatalogLayoutVipBuyView: FC<CatalogLayoutProps> = props =>
<Text fontWeight="bold">{ getPurchaseHeader() }</Text> <Text fontWeight="bold">{ getPurchaseHeader() }</Text>
<Text>{ getPurchaseValidUntil() }</Text> <Text>{ getPurchaseValidUntil() }</Text>
</Column> </Column>
<Column gap={ 1 }> <div className="flex flex-column gap-1">
{ (pendingOffer.priceCredits > 0) && { (pendingOffer.priceCredits > 0) &&
<Flex alignItems="center" justifyContent="end" gap={ 1 }> <Flex alignItems="center" gap={ 1 } justifyContent="end">
<Text>{ pendingOffer.priceCredits }</Text> <Text>{ pendingOffer.priceCredits }</Text>
<LayoutCurrencyIcon type={ -1 } /> <LayoutCurrencyIcon type={ -1 } />
</Flex> } </Flex> }
{ (pendingOffer.priceActivityPoints > 0) && { (pendingOffer.priceActivityPoints > 0) &&
<Flex alignItems="center" justifyContent="end" gap={ 1 }> <Flex alignItems="center" gap={ 1 } justifyContent="end">
<Text>{ pendingOffer.priceActivityPoints }</Text> <Text>{ pendingOffer.priceActivityPoints }</Text>
<LayoutCurrencyIcon type={ pendingOffer.priceActivityPointsType } /> <LayoutCurrencyIcon type={ pendingOffer.priceActivityPointsType } />
</Flex> } </Flex> }
</Column> </div>
</Flex> </Flex>
{ getPurchaseButton() } { getPurchaseButton() }
</Column> } </Column> }

View File

@ -27,8 +27,8 @@ export const CatalogLayoutFrontPageItemView: FC<CatalogLayoutFrontPageItemViewPr
const imageUrl = (GetConfigurationValue<string>('image.library.url') + item.itemPromoImage); const imageUrl = (GetConfigurationValue<string>('image.library.url') + item.itemPromoImage);
return ( return (
<LayoutBackgroundImage imageUrl={ imageUrl } classNames={ getClassNames } position={ position } fullHeight={ fullHeight } pointer={ pointer } overflow={ overflow } { ...rest }> <LayoutBackgroundImage classNames={ getClassNames } fullHeight={ fullHeight } imageUrl={ imageUrl } overflow={ overflow } pointer={ pointer } position={ position } { ...rest }>
<Text position="absolute" variant="white" className="bg-dark rounded p-2 m-2 bottom-0"> <Text className="bg-dark rounded p-2 m-2 bottom-0" position="absolute" variant="white">
{ item.itemName } { item.itemName }
</Text> </Text>
{ children } { children }

View File

@ -1,5 +1,5 @@
import { FC, useCallback, useMemo } from 'react'; import { FC, useCallback, useMemo } from 'react';
import { GetImageIconUrlForProduct, LocalizeText, MarketplaceOfferData, MarketPlaceOfferState, ProductTypeEnum } from '../../../../../../api'; import { GetImageIconUrlForProduct, LocalizeText, MarketPlaceOfferState, MarketplaceOfferData, ProductTypeEnum } from '../../../../../../api';
import { Button, Column, LayoutGridItem, Text } from '../../../../../../common'; import { Button, Column, LayoutGridItem, Text } from '../../../../../../common';
export interface MarketplaceItemViewProps export interface MarketplaceItemViewProps
@ -46,7 +46,7 @@ export const CatalogLayoutMarketplaceItemView: FC<MarketplaceItemViewProps> = pr
}, [ offerData ]); }, [ offerData ]);
return ( return (
<LayoutGridItem shrink center={ false } column={ false } alignItems="center" className="p-1"> <LayoutGridItem shrink alignItems="center" center={ false } className="p-1" column={ false }>
<Column style={ { width: 40, height: 40 } }> <Column style={ { width: 40, height: 40 } }>
<LayoutGridItem column={ false } itemImage={ GetImageIconUrlForProduct(((offerData.furniType === MarketplaceOfferData.TYPE_FLOOR) ? ProductTypeEnum.FLOOR : ProductTypeEnum.WALL), offerData.furniId, offerData.extraData) } itemUniqueNumber={ offerData.isUniqueLimitedItem ? offerData.stuffData.uniqueNumber : 0 } /> <LayoutGridItem column={ false } itemImage={ GetImageIconUrlForProduct(((offerData.furniType === MarketplaceOfferData.TYPE_FLOOR) ? ProductTypeEnum.FLOOR : ProductTypeEnum.WALL), offerData.furniId, offerData.extraData) } itemUniqueNumber={ offerData.isUniqueLimitedItem ? offerData.stuffData.uniqueNumber : 0 } />
</Column> </Column>
@ -63,7 +63,7 @@ export const CatalogLayoutMarketplaceItemView: FC<MarketplaceItemViewProps> = pr
<Text>{ LocalizeText('catalog.marketplace.offer_count', [ 'count' ], [ offerData.offerCount.toString() ]) }</Text> <Text>{ LocalizeText('catalog.marketplace.offer_count', [ 'count' ], [ offerData.offerCount.toString() ]) }</Text>
</> } </> }
</Column> </Column>
<Column gap={ 1 }> <div className="flex flex-column gap-1">
{ ((type === OWN_OFFER) && (offerData.status !== MarketPlaceOfferState.SOLD)) && { ((type === OWN_OFFER) && (offerData.status !== MarketPlaceOfferState.SOLD)) &&
<Button variant="secondary" onClick={ () => onClick(offerData) }> <Button variant="secondary" onClick={ () => onClick(offerData) }>
{ LocalizeText('catalog.marketplace.offer.pick') } { LocalizeText('catalog.marketplace.offer.pick') }
@ -73,11 +73,11 @@ export const CatalogLayoutMarketplaceItemView: FC<MarketplaceItemViewProps> = pr
<Button variant="secondary" onClick={ () => onClick(offerData) }> <Button variant="secondary" onClick={ () => onClick(offerData) }>
{ LocalizeText('buy') } { LocalizeText('buy') }
</Button> </Button>
<Button variant="secondary" disabled> <Button disabled variant="secondary">
{ LocalizeText('catalog.marketplace.view_more') } { LocalizeText('catalog.marketplace.view_more') }
</Button> </Button>
</> } </> }
</Column> </div>
</LayoutGridItem> </LayoutGridItem>
); );
} }

View File

@ -81,7 +81,7 @@ export const CatalogLayoutMarketplaceOwnItemsView: FC<CatalogLayoutProps> = prop
{ LocalizeText('catalog.marketplace.redeem.no_sold_items') } { LocalizeText('catalog.marketplace.redeem.no_sold_items') }
</Text> } </Text> }
{ (creditsWaiting > 0) && { (creditsWaiting > 0) &&
<Column center gap={ 1 } className="bg-muted rounded p-2"> <Column center className="bg-muted rounded p-2" gap={ 1 }>
<Text> <Text>
{ LocalizeText('catalog.marketplace.redeem.get_credits', [ 'count', 'credits' ], [ soldOffers.length.toString(), creditsWaiting.toString() ]) } { LocalizeText('catalog.marketplace.redeem.get_credits', [ 'count', 'credits' ], [ soldOffers.length.toString(), creditsWaiting.toString() ]) }
</Text> </Text>
@ -90,10 +90,10 @@ export const CatalogLayoutMarketplaceOwnItemsView: FC<CatalogLayoutProps> = prop
</Button> </Button>
</Column> } </Column> }
<Column gap={ 1 } overflow="hidden"> <Column gap={ 1 } overflow="hidden">
<Text truncate shrink fontWeight="bold"> <Text shrink truncate fontWeight="bold">
{ LocalizeText('catalog.marketplace.items_found', [ 'count' ], [ offers.length.toString() ]) } { LocalizeText('catalog.marketplace.items_found', [ 'count' ], [ offers.length.toString() ]) }
</Text> </Text>
<Column overflow="auto" className="nitro-catalog-layout-marketplace-grid"> <Column className="nitro-catalog-layout-marketplace-grid" overflow="auto">
{ (offers.length > 0) && offers.map(offer => <CatalogLayoutMarketplaceItemView key={ offer.offerId } offerData={ offer } type={ OWN_OFFER } onClick={ takeItemBack } />) } { (offers.length > 0) && offers.map(offer => <CatalogLayoutMarketplaceItemView key={ offer.offerId } offerData={ offer } type={ OWN_OFFER } onClick={ takeItemBack } />) }
</Column> </Column>
</Column> </Column>

View File

@ -1,7 +1,7 @@
import { BuyMarketplaceOfferMessageComposer, GetMarketplaceOffersMessageComposer, MarketplaceBuyOfferResultEvent, MarketPlaceOffersEvent } from '@nitrots/nitro-renderer'; import { BuyMarketplaceOfferMessageComposer, GetMarketplaceOffersMessageComposer, MarketplaceBuyOfferResultEvent, MarketPlaceOffersEvent } from '@nitrots/nitro-renderer';
import { FC, useCallback, useMemo, useState } from 'react'; import { FC, useCallback, useMemo, useState } from 'react';
import { IMarketplaceSearchOptions, LocalizeText, MarketplaceOfferData, MarketplaceSearchType, NotificationAlertType, SendMessageComposer } from '../../../../../../api'; import { IMarketplaceSearchOptions, LocalizeText, MarketplaceOfferData, MarketplaceSearchType, NotificationAlertType, SendMessageComposer } from '../../../../../../api';
import { Button, ButtonGroup, Column, Text } from '../../../../../../common'; import { Button, Column, Text } from '../../../../../../common';
import { useMessageEvent, useNotification, usePurse } from '../../../../../../hooks'; import { useMessageEvent, useNotification, usePurse } from '../../../../../../hooks';
import { CatalogLayoutProps } from '../CatalogLayout.types'; import { CatalogLayoutProps } from '../CatalogLayout.types';
import { CatalogLayoutMarketplaceItemView, PUBLIC_OFFER } from './CatalogLayoutMarketplaceItemView'; import { CatalogLayoutMarketplaceItemView, PUBLIC_OFFER } from './CatalogLayoutMarketplaceItemView';
@ -134,7 +134,7 @@ export const CatalogLayoutMarketplacePublicItemsView: FC<CatalogLayoutMarketplac
return ( return (
<> <>
<ButtonGroup> <div className="btn-group">
<Button active={ (searchType === MarketplaceSearchType.BY_ACTIVITY) } onClick={ () => setSearchType(MarketplaceSearchType.BY_ACTIVITY) }> <Button active={ (searchType === MarketplaceSearchType.BY_ACTIVITY) } onClick={ () => setSearchType(MarketplaceSearchType.BY_ACTIVITY) }>
{ LocalizeText('catalog.marketplace.search_by_activity') } { LocalizeText('catalog.marketplace.search_by_activity') }
</Button> </Button>
@ -144,10 +144,10 @@ export const CatalogLayoutMarketplacePublicItemsView: FC<CatalogLayoutMarketplac
<Button active={ (searchType === MarketplaceSearchType.ADVANCED) } onClick={ () => setSearchType(MarketplaceSearchType.ADVANCED) }> <Button active={ (searchType === MarketplaceSearchType.ADVANCED) } onClick={ () => setSearchType(MarketplaceSearchType.ADVANCED) }>
{ LocalizeText('catalog.marketplace.search_advanced') } { LocalizeText('catalog.marketplace.search_advanced') }
</Button> </Button>
</ButtonGroup> </div>
<SearchFormView sortTypes={ getSortTypes } searchType={ searchType } onSearch={ requestOffers } /> <SearchFormView searchType={ searchType } sortTypes={ getSortTypes } onSearch={ requestOffers } />
<Column gap={ 1 } overflow="hidden"> <Column gap={ 1 } overflow="hidden">
<Text truncate shrink fontWeight="bold"> <Text shrink truncate fontWeight="bold">
{ LocalizeText('catalog.marketplace.items_found', [ 'count' ], [ offers.size.toString() ]) } { LocalizeText('catalog.marketplace.items_found', [ 'count' ], [ offers.size.toString() ]) }
</Text> </Text>
<Column className="nitro-catalog-layout-marketplace-grid" overflow="auto"> <Column className="nitro-catalog-layout-marketplace-grid" overflow="auto">

View File

@ -1,6 +1,6 @@
import { FC, useCallback, useEffect, useState } from 'react'; import { FC, useCallback, useEffect, useState } from 'react';
import { IMarketplaceSearchOptions, LocalizeText, MarketplaceSearchType } from '../../../../../../api'; import { IMarketplaceSearchOptions, LocalizeText, MarketplaceSearchType } from '../../../../../../api';
import { Button, Column, Flex, Text } from '../../../../../../common'; import { Button, Text } from '../../../../../../common';
export interface SearchFormViewProps export interface SearchFormViewProps
{ {
@ -44,28 +44,28 @@ export const SearchFormView: FC<SearchFormViewProps> = props =>
}, [ onSearch, searchType, sortTypes ]); }, [ onSearch, searchType, sortTypes ]);
return ( return (
<Column gap={ 1 }> <div className="flex flex-column gap-1">
<Flex alignItems="center" gap={ 1 }> <div className="flex items-center gap-1">
<Text className="col-3">{ LocalizeText('catalog.marketplace.sort_order') }</Text> <Text className="col-3">{ LocalizeText('catalog.marketplace.sort_order') }</Text>
<select className="form-select form-select-sm" value={ sortType } onChange={ event => onSortTypeChange(parseInt(event.target.value)) }> <select className="form-select form-select-sm" value={ sortType } onChange={ event => onSortTypeChange(parseInt(event.target.value)) }>
{ sortTypes.map(type => <option key={ type } value={ type }>{ LocalizeText(`catalog.marketplace.sort.${ type }`) }</option>) } { sortTypes.map(type => <option key={ type } value={ type }>{ LocalizeText(`catalog.marketplace.sort.${ type }`) }</option>) }
</select> </select>
</Flex> </div>
{ searchType === MarketplaceSearchType.ADVANCED && { searchType === MarketplaceSearchType.ADVANCED &&
<> <>
<Flex alignItems="center" gap={ 1 }> <div className="flex items-center gap-1">
<Text className="col-3">{ LocalizeText('catalog.marketplace.search_name') }</Text> <Text className="col-3">{ LocalizeText('catalog.marketplace.search_name') }</Text>
<input className="form-control form-control-sm" type="text" value={ searchQuery } onChange={ event => setSearchQuery(event.target.value) }/> <input className="form-control form-control-sm" type="text" value={ searchQuery } onChange={ event => setSearchQuery(event.target.value) }/>
</Flex> </div>
<Flex alignItems="center" gap={ 1 }> <div className="flex items-center gap-1">
<Text className="col-3">{ LocalizeText('catalog.marketplace.search_price') }</Text> <Text className="col-3">{ LocalizeText('catalog.marketplace.search_price') }</Text>
<Flex fullWidth gap={ 1 }> <div className="flex w-100 gap-1">
<input className="form-control form-control-sm" type="number" min={ 0 } value={ min } onChange={ event => setMin(event.target.valueAsNumber) } /> <input className="form-control form-control-sm" min={ 0 } type="number" value={ min } onChange={ event => setMin(event.target.valueAsNumber) } />
<input className="form-control form-control-sm" type="number" min={ 0 } value={ max } onChange={ event => setMax(event.target.valueAsNumber) } /> <input className="form-control form-control-sm" min={ 0 } type="number" value={ max } onChange={ event => setMax(event.target.valueAsNumber) } />
</Flex> </div>
</Flex> </div>
<Button variant="secondary" className="mx-auto" onClick={ onClickSearch }>{ LocalizeText('generic.search') }</Button> <Button className="mx-auto" variant="secondary" onClick={ onClickSearch }>{ LocalizeText('generic.search') }</Button>
</> } </> }
</Column> </div>
); );
} }

View File

@ -1,7 +1,7 @@
import { GetMarketplaceConfigurationMessageComposer, MakeOfferMessageComposer, MarketplaceConfigurationEvent } from '@nitrots/nitro-renderer'; import { GetMarketplaceConfigurationMessageComposer, MakeOfferMessageComposer, MarketplaceConfigurationEvent } from '@nitrots/nitro-renderer';
import { FC, useEffect, useState } from 'react'; import { FC, useEffect, useState } from 'react';
import { FurnitureItem, LocalizeText, ProductTypeEnum, SendMessageComposer } from '../../../../../../api'; import { FurnitureItem, LocalizeText, ProductTypeEnum, SendMessageComposer } from '../../../../../../api';
import { Base, Button, Column, Grid, LayoutFurniImageView, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../../../../common'; import { Button, Column, Grid, LayoutFurniImageView, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../../../../common';
import { CatalogPostMarketplaceOfferEvent } from '../../../../../../events'; import { CatalogPostMarketplaceOfferEvent } from '../../../../../../events';
import { useCatalog, useMessageEvent, useNotification, useUiEvent } from '../../../../../../hooks'; import { useCatalog, useMessageEvent, useNotification, useUiEvent } from '../../../../../../hooks';
@ -82,32 +82,32 @@ export const MarketplacePostOfferView : FC<{}> = props =>
<NitroCardHeaderView headerText={ LocalizeText('inventory.marketplace.make_offer.title') } onCloseClick={ event => setItem(null) } /> <NitroCardHeaderView headerText={ LocalizeText('inventory.marketplace.make_offer.title') } onCloseClick={ event => setItem(null) } />
<NitroCardContentView overflow="hidden"> <NitroCardContentView overflow="hidden">
<Grid fullHeight> <Grid fullHeight>
<Column center className="bg-muted rounded p-2" size={ 4 } overflow="hidden"> <Column center className="bg-muted rounded p-2" overflow="hidden" size={ 4 }>
<LayoutFurniImageView productType={ item.isWallItem ? ProductTypeEnum.WALL : ProductTypeEnum.FLOOR } productClassId={ item.type } extraData={ item.extra.toString() } /> <LayoutFurniImageView extraData={ item.extra.toString() } productClassId={ item.type } productType={ item.isWallItem ? ProductTypeEnum.WALL : ProductTypeEnum.FLOOR } />
</Column> </Column>
<Column size={ 8 } justifyContent="between" overflow="hidden"> <Column justifyContent="between" overflow="hidden" size={ 8 }>
<Column grow gap={ 1 }> <Column grow gap={ 1 }>
<Text fontWeight="bold">{ getFurniTitle }</Text> <Text fontWeight="bold">{ getFurniTitle }</Text>
<Text truncate shrink>{ getFurniDescription }</Text> <Text shrink truncate>{ getFurniDescription }</Text>
</Column> </Column>
<Column overflow="auto"> <Column overflow="auto">
<Text italics> <Text italics>
{ LocalizeText('inventory.marketplace.make_offer.expiration_info', [ 'time' ], [ marketplaceConfiguration.offerTime.toString() ]) } { LocalizeText('inventory.marketplace.make_offer.expiration_info', [ 'time' ], [ marketplaceConfiguration.offerTime.toString() ]) }
</Text> </Text>
<div className="input-group has-validation"> <div className="input-group has-validation">
<input className="form-control form-control-sm" type="number" min={ 0 } value={ tempAskingPrice } onChange={ event => updateAskingPrice(event.target.value) } placeholder={ LocalizeText('inventory.marketplace.make_offer.price_request') } /> <input className="form-control form-control-sm" min={ 0 } placeholder={ LocalizeText('inventory.marketplace.make_offer.price_request') } type="number" value={ tempAskingPrice } onChange={ event => updateAskingPrice(event.target.value) } />
{ ((askingPrice < marketplaceConfiguration.minimumPrice) || isNaN(askingPrice)) && { ((askingPrice < marketplaceConfiguration.minimumPrice) || isNaN(askingPrice)) &&
<Base className="invalid-feedback d-block"> <div className="invalid-feedback d-block">
{ LocalizeText('inventory.marketplace.make_offer.min_price', [ 'minprice' ], [ marketplaceConfiguration.minimumPrice.toString() ]) } { LocalizeText('inventory.marketplace.make_offer.min_price', [ 'minprice' ], [ marketplaceConfiguration.minimumPrice.toString() ]) }
</Base> } </div> }
{ ((askingPrice > marketplaceConfiguration.maximumPrice) && !isNaN(askingPrice)) && { ((askingPrice > marketplaceConfiguration.maximumPrice) && !isNaN(askingPrice)) &&
<Base className="invalid-feedback d-block"> <div className="invalid-feedback d-block">
{ LocalizeText('inventory.marketplace.make_offer.max_price', [ 'maxprice' ], [ marketplaceConfiguration.maximumPrice.toString() ]) } { LocalizeText('inventory.marketplace.make_offer.max_price', [ 'maxprice' ], [ marketplaceConfiguration.maximumPrice.toString() ]) }
</Base> } </div> }
{ (!((askingPrice < marketplaceConfiguration.minimumPrice) || (askingPrice > marketplaceConfiguration.maximumPrice) || isNaN(askingPrice))) && { (!((askingPrice < marketplaceConfiguration.minimumPrice) || (askingPrice > marketplaceConfiguration.maximumPrice) || isNaN(askingPrice))) &&
<Base className="invalid-feedback d-block"> <div className="invalid-feedback d-block">
{ LocalizeText('inventory.marketplace.make_offer.final_price', [ 'commission', 'finalprice' ], [ getCommission().toString(), (askingPrice + getCommission()).toString() ]) } { LocalizeText('inventory.marketplace.make_offer.final_price', [ 'commission', 'finalprice' ], [ getCommission().toString(), (askingPrice + getCommission()).toString() ]) }
</Base> } </div> }
</div> </div>
<Button disabled={ ((askingPrice < marketplaceConfiguration.minimumPrice) || (askingPrice > marketplaceConfiguration.maximumPrice) || isNaN(askingPrice)) } onClick={ postItem }> <Button disabled={ ((askingPrice < marketplaceConfiguration.minimumPrice) || (askingPrice > marketplaceConfiguration.maximumPrice) || isNaN(askingPrice)) } onClick={ postItem }>
{ LocalizeText('inventory.marketplace.make_offer.post') } { LocalizeText('inventory.marketplace.make_offer.post') }

View File

@ -2,7 +2,7 @@ import { ApproveNameMessageComposer, ApproveNameMessageEvent, ColorConverter, Ge
import { FC, useCallback, useEffect, useMemo, useState } from 'react'; import { FC, useCallback, useEffect, useMemo, useState } from 'react';
import { FaFillDrip } from 'react-icons/fa'; import { FaFillDrip } from 'react-icons/fa';
import { DispatchUiEvent, GetPetAvailableColors, GetPetIndexFromLocalization, LocalizeText, SendMessageComposer } from '../../../../../../api'; import { DispatchUiEvent, GetPetAvailableColors, GetPetIndexFromLocalization, LocalizeText, SendMessageComposer } from '../../../../../../api';
import { AutoGrid, Base, Button, Column, Flex, Grid, LayoutGridItem, LayoutPetImageView, Text } from '../../../../../../common'; import { AutoGrid, Button, Column, Grid, LayoutGridItem, LayoutPetImageView, Text } from '../../../../../../common';
import { CatalogPurchaseFailureEvent } from '../../../../../../events'; import { CatalogPurchaseFailureEvent } from '../../../../../../events';
import { useCatalog, useMessageEvent } from '../../../../../../hooks'; import { useCatalog, useMessageEvent } from '../../../../../../hooks';
import { CatalogAddOnBadgeWidgetView } from '../../widgets/CatalogAddOnBadgeWidgetView'; import { CatalogAddOnBadgeWidgetView } from '../../widgets/CatalogAddOnBadgeWidgetView';
@ -195,20 +195,20 @@ export const CatalogLayoutPetView: FC<CatalogLayoutProps> = props =>
return ( return (
<Grid> <Grid>
<Column size={ 7 } overflow="hidden"> <Column overflow="hidden" size={ 7 }>
<AutoGrid columnCount={ 5 }> <AutoGrid columnCount={ 5 }>
{ !colorsShowing && (sellablePalettes.length > 0) && sellablePalettes.map((palette, index) => { !colorsShowing && (sellablePalettes.length > 0) && sellablePalettes.map((palette, index) =>
{ {
return ( return (
<LayoutGridItem key={ index } itemActive={ (selectedPaletteIndex === index) } onClick={ event => setSelectedPaletteIndex(index) }> <LayoutGridItem key={ index } itemActive={ (selectedPaletteIndex === index) } onClick={ event => setSelectedPaletteIndex(index) }>
<LayoutPetImageView typeId={ petIndex } paletteId={ palette.paletteId } direction={ 2 } headOnly={ true } /> <LayoutPetImageView direction={ 2 } headOnly={ true } paletteId={ palette.paletteId } typeId={ petIndex } />
</LayoutGridItem> </LayoutGridItem>
); );
}) } }) }
{ colorsShowing && (sellableColors.length > 0) && sellableColors.map((colorSet, index) => <LayoutGridItem itemHighlight key={ index } itemActive={ (selectedColorIndex === index) } itemColor={ ColorConverter.int2rgb(colorSet[0]) } className="clear-bg" onClick={ event => setSelectedColorIndex(index) } />) } { colorsShowing && (sellableColors.length > 0) && sellableColors.map((colorSet, index) => <LayoutGridItem key={ index } itemHighlight className="clear-bg" itemActive={ (selectedColorIndex === index) } itemColor={ ColorConverter.int2rgb(colorSet[0]) } onClick={ event => setSelectedColorIndex(index) } />) }
</AutoGrid> </AutoGrid>
</Column> </Column>
<Column center={ !currentOffer } size={ 5 } overflow="hidden"> <Column center={ !currentOffer } overflow="hidden" size={ 5 }>
{ !currentOffer && { !currentOffer &&
<> <>
{ !!page.localization.getImage(1) && <img alt="" src={ page.localization.getImage(1) } /> } { !!page.localization.getImage(1) && <img alt="" src={ page.localization.getImage(1) } /> }
@ -216,24 +216,24 @@ export const CatalogLayoutPetView: FC<CatalogLayoutProps> = props =>
</> } </> }
{ currentOffer && { currentOffer &&
<> <>
<Base position="relative" overflow="hidden"> <div className="position-relative overflow-hidden">
<CatalogViewProductWidgetView /> <CatalogViewProductWidgetView />
<CatalogAddOnBadgeWidgetView position="absolute" className="bg-muted rounded bottom-1 end-1" /> <CatalogAddOnBadgeWidgetView className="bg-muted rounded bottom-1 end-1" position="absolute" />
{ ((petIndex > -1) && (petIndex <= 7)) && { ((petIndex > -1) && (petIndex <= 7)) &&
<Button position="absolute" className="bottom-1 start-1" onClick={ event => setColorsShowing(!colorsShowing) }> <Button className="bottom-1 start-1" position="absolute" onClick={ event => setColorsShowing(!colorsShowing) }>
<FaFillDrip className="fa-icon" /> <FaFillDrip className="fa-icon" />
</Button> } </Button> }
</Base> </div>
<Column grow gap={ 1 }> <Column grow gap={ 1 }>
<Text truncate>{ petBreedName }</Text> <Text truncate>{ petBreedName }</Text>
<Column grow gap={ 1 }> <Column grow gap={ 1 }>
<input type="text" className="form-control form-control-sm w-100" placeholder={ LocalizeText('widgets.petpackage.name.title') } value={ petName } onChange={ event => setPetName(event.target.value) } /> <input className="form-control form-control-sm w-100" placeholder={ LocalizeText('widgets.petpackage.name.title') } type="text" value={ petName } onChange={ event => setPetName(event.target.value) } />
{ (approvalResult > 0) && { (approvalResult > 0) &&
<Base className="invalid-feedback d-block m-0">{ validationErrorMessage }</Base> } <div className="invalid-feedback d-block m-0">{ validationErrorMessage }</div> }
</Column> </Column>
<Flex justifyContent="end"> <div className="flex justify-content-end">
<CatalogTotalPriceWidget justifyContent="end" alignItems="end" /> <CatalogTotalPriceWidget alignItems="end" justifyContent="end" />
</Flex> </div>
<CatalogPurchaseWidgetView purchaseCallback={ purchasePet } /> <CatalogPurchaseWidgetView purchaseCallback={ purchasePet } />
</Column> </Column>
</> } </> }

View File

@ -53,9 +53,9 @@ export const CatalogLayoutVipGiftsView: FC<CatalogLayoutProps> = props =>
return ( return (
<> <>
<Text truncate shrink fontWeight="bold">{ giftsAvailable() }</Text> <Text shrink truncate fontWeight="bold">{ giftsAvailable() }</Text>
<AutoGrid columnCount={ 1 } className="nitro-catalog-layout-vip-gifts-grid"> <AutoGrid className="nitro-catalog-layout-vip-gifts-grid" columnCount={ 1 }>
{ (clubGifts.offers.length > 0) && sortGifts.map(offer => <VipGiftItem key={ offer.offerId } offer={ offer } isAvailable={ (clubGifts.getOfferExtraData(offer.offerId).isSelectable && (clubGifts.giftsAvailable > 0)) } onSelect={ selectGift } daysRequired={ clubGifts.getOfferExtraData(offer.offerId).daysRequired }/>) } { (clubGifts.offers.length > 0) && sortGifts.map(offer => <VipGiftItem key={ offer.offerId } daysRequired={ clubGifts.getOfferExtraData(offer.offerId).daysRequired } isAvailable={ (clubGifts.getOfferExtraData(offer.offerId).isSelectable && (clubGifts.giftsAvailable > 0)) } offer={ offer } onSelect={ selectGift }/>) }
</AutoGrid> </AutoGrid>
</> </>
) )

View File

@ -52,10 +52,10 @@ export const VipGiftItem : FC<VipGiftItemViewProps> = props =>
},[ daysRequired ]); },[ daysRequired ]);
return ( return (
<LayoutGridItem center={ false } column={ false } alignItems="center" className="p-1"> <LayoutGridItem alignItems="center" center={ false } className="p-1" column={ false }>
<LayoutImage imageUrl={ getImageUrlForOffer() } /> <LayoutImage imageUrl={ getImageUrlForOffer() } />
<Text grow fontWeight="bold">{ getItemTitle() }</Text> <Text grow fontWeight="bold">{ getItemTitle() }</Text>
<Button variant="secondary" onClick={ () => onSelect(offer.localizationId) } disabled={ !isAvailable }> <Button disabled={ !isAvailable } variant="secondary" onClick={ () => onSelect(offer.localizationId) }>
{ LocalizeText('catalog.club_gift.select') } { LocalizeText('catalog.club_gift.select') }
</Button> </Button>
</LayoutGridItem> </LayoutGridItem>

View File

@ -21,8 +21,8 @@ export const CatalogBundleGridWidgetView: FC<CatalogBundleGridWidgetViewProps> =
if(!currentOffer) return null; if(!currentOffer) return null;
return ( return (
<AutoGrid innerRef={ elementRef } columnCount={ 5 } { ...rest }> <AutoGrid columnCount={ 5 } innerRef={ elementRef } { ...rest }>
{ currentOffer.products && (currentOffer.products.length > 0) && currentOffer.products.map((product, index) => <LayoutGridItem key={ index } itemImage={ product.getIconUrl() } itemCount={ product.productCount } />) } { currentOffer.products && (currentOffer.products.length > 0) && currentOffer.products.map((product, index) => <LayoutGridItem key={ index } itemCount={ product.productCount } itemImage={ product.getIconUrl() } />) }
{ children } { children }
</AutoGrid> </AutoGrid>
); );

View File

@ -1,7 +1,7 @@
import { CatalogGroupsComposer, StringDataType } from '@nitrots/nitro-renderer'; import { CatalogGroupsComposer, StringDataType } from '@nitrots/nitro-renderer';
import { FC, useEffect, useMemo, useState } from 'react'; import { FC, useEffect, useMemo, useState } from 'react';
import { LocalizeText, SendMessageComposer } from '../../../../../api'; import { LocalizeText, SendMessageComposer } from '../../../../../api';
import { Base, Button, Flex } from '../../../../../common'; import { Button, Flex } from '../../../../../common';
import { useCatalog } from '../../../../../hooks'; import { useCatalog } from '../../../../../hooks';
export const CatalogGuildSelectorWidgetView: FC<{}> = props => export const CatalogGuildSelectorWidgetView: FC<{}> = props =>
@ -49,27 +49,27 @@ export const CatalogGuildSelectorWidgetView: FC<{}> = props =>
if(!groups || !groups.length) if(!groups || !groups.length)
{ {
return ( return (
<Base className="bg-muted rounded p-1 text-black text-center"> <div className="bg-muted rounded p-1 text-black text-center">
{ LocalizeText('catalog.guild_selector.members_only') } { LocalizeText('catalog.guild_selector.members_only') }
<Button className="mt-1"> <Button className="mt-1">
{ LocalizeText('catalog.guild_selector.find_groups') } { LocalizeText('catalog.guild_selector.find_groups') }
</Button> </Button>
</Base> </div>
); );
} }
const selectedGroup = groups[selectedGroupIndex]; const selectedGroup = groups[selectedGroupIndex];
return ( return (
<Flex gap={ 1 }> <div className="flex gap-1">
{ !!selectedGroup && { !!selectedGroup &&
<Flex overflow="hidden" className="rounded border"> <Flex className="rounded border" overflow="hidden">
<Base fullHeight style={ { width: '20px', backgroundColor: '#' + selectedGroup.colorA } } /> <div className="h-100" style={ { width: '20px', backgroundColor: '#' + selectedGroup.colorA } } />
<Base fullHeight style={ { width: '20px', backgroundColor: '#' + selectedGroup.colorB } } /> <div className="h-100" style={ { width: '20px', backgroundColor: '#' + selectedGroup.colorB } } />
</Flex> } </Flex> }
<select className="form-select form-select-sm" value={ selectedGroupIndex } onChange={ event => setSelectedGroupIndex(parseInt(event.target.value)) }> <select className="form-select form-select-sm" value={ selectedGroupIndex } onChange={ event => setSelectedGroupIndex(parseInt(event.target.value)) }>
{ groups.map((group, index) => <option key={ index } value={ index }>{ group.groupName }</option>) } { groups.map((group, index) => <option key={ index } value={ index }>{ group.groupName }</option>) }
</select> </select>
</Flex> </div>
); );
} }

View File

@ -44,7 +44,7 @@ export const CatalogItemGridWidgetView: FC<CatalogItemGridWidgetViewProps> = pro
} }
return ( return (
<AutoGrid innerRef={ elementRef } columnCount={ columnCount } { ...rest }> <AutoGrid columnCount={ columnCount } innerRef={ elementRef } { ...rest }>
{ currentPage.offers && (currentPage.offers.length > 0) && currentPage.offers.map((offer, index) => <CatalogGridOfferView key={ index } itemActive={ (currentOffer && (currentOffer.offerId === offer.offerId)) } offer={ offer } selectOffer={ selectOffer } />) } { currentPage.offers && (currentPage.offers.length > 0) && currentPage.offers.map((offer, index) => <CatalogGridOfferView key={ index } itemActive={ (currentOffer && (currentOffer.offerId === offer.offerId)) } offer={ offer } selectOffer={ selectOffer } />) }
{ children } { children }
</AutoGrid> </AutoGrid>

View File

@ -1,19 +1,17 @@
import { FC } from 'react'; import { FC } from 'react';
import { Offer } from '../../../../../api'; import { Offer } from '../../../../../api';
import { Base, BaseProps, LayoutLimitedEditionCompletePlateView } from '../../../../../common'; import { LayoutLimitedEditionCompletePlateView } from '../../../../../common';
import { useCatalog } from '../../../../../hooks'; import { useCatalog } from '../../../../../hooks';
export const CatalogLimitedItemWidgetView: FC<BaseProps<HTMLDivElement>> = props => export const CatalogLimitedItemWidgetView: FC = props =>
{ {
const { children = null, ...rest } = props;
const { currentOffer = null } = useCatalog(); const { currentOffer = null } = useCatalog();
if(!currentOffer || (currentOffer.pricingModel !== Offer.PRICING_MODEL_SINGLE) || !currentOffer.product.isUniqueLimitedItem) return null; if(!currentOffer || (currentOffer.pricingModel !== Offer.PRICING_MODEL_SINGLE) || !currentOffer.product.isUniqueLimitedItem) return null;
return ( return (
<Base { ...rest }> <div className="w-100">
<LayoutLimitedEditionCompletePlateView className="mx-auto" uniqueLimitedItemsLeft={ currentOffer.product.uniqueLimitedItemsLeft } uniqueLimitedSeriesSize={ currentOffer.product.uniqueLimitedItemSeriesSize } /> <LayoutLimitedEditionCompletePlateView className="mx-auto" uniqueLimitedItemsLeft={ currentOffer.product.uniqueLimitedItemsLeft } uniqueLimitedSeriesSize={ currentOffer.product.uniqueLimitedItemSeriesSize } />
{ children } </div>
</Base>
); );
} }

View File

@ -1,7 +1,7 @@
import { FC } from 'react'; import { FC } from 'react';
import { FaPlus } from 'react-icons/fa'; import { FaPlus } from 'react-icons/fa';
import { IPurchasableOffer } from '../../../../../api'; import { IPurchasableOffer } from '../../../../../api';
import { Flex, LayoutCurrencyIcon, Text } from '../../../../../common'; import { LayoutCurrencyIcon, Text } from '../../../../../common';
import { useCatalog } from '../../../../../hooks'; import { useCatalog } from '../../../../../hooks';
interface CatalogPriceDisplayWidgetViewProps interface CatalogPriceDisplayWidgetViewProps
@ -21,17 +21,17 @@ export const CatalogPriceDisplayWidgetView: FC<CatalogPriceDisplayWidgetViewProp
return ( return (
<> <>
{ (offer.priceInCredits > 0) && { (offer.priceInCredits > 0) &&
<Flex alignItems="center" gap={ 1 }> <div className="flex items-center gap-1">
<Text bold>{ (offer.priceInCredits * quantity) }</Text> <Text bold>{ (offer.priceInCredits * quantity) }</Text>
<LayoutCurrencyIcon type={ -1 } /> <LayoutCurrencyIcon type={ -1 } />
</Flex> } </div> }
{ separator && (offer.priceInCredits > 0) && (offer.priceInActivityPoints > 0) && { separator && (offer.priceInCredits > 0) && (offer.priceInActivityPoints > 0) &&
<FaPlus size="xs" color="black" className="fa-icon" /> } <FaPlus className="fa-icon" color="black" size="xs" /> }
{ (offer.priceInActivityPoints > 0) && { (offer.priceInActivityPoints > 0) &&
<Flex alignItems="center" gap={ 1 }> <div className="flex items-center gap-1">
<Text bold>{ (offer.priceInActivityPoints * quantity) }</Text> <Text bold>{ (offer.priceInActivityPoints * quantity) }</Text>
<LayoutCurrencyIcon type={ offer.activityPointType } /> <LayoutCurrencyIcon type={ offer.activityPointType } />
</Flex> } </div> }
</> </>
); );
} }

Some files were not shown because too many files have changed in this diff Show More