mirror of
https://github.com/billsonnn/nitro-react.git
synced 2024-11-22 22:30:52 +01:00
Merge branch 'dev' into 'main'
Release 2.1.1 Closes #252 See merge request nitro/nitro-react!74
This commit is contained in:
commit
568e7bc002
@ -1,6 +1,7 @@
|
||||
{
|
||||
"name": "nitro-react",
|
||||
"version": "2.1.0",
|
||||
"version": "2.1.1",
|
||||
"homepage": ".",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "cross-env SKIP_PREFLIGHT_CHECK=true BROWSER=none IMAGE_INLINE_SIZE_LIMIT=100000 craco --openssl-legacy-provider start",
|
||||
|
@ -15,6 +15,7 @@
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
||||
<base href="./">
|
||||
<title>Nitro</title>
|
||||
</head>
|
||||
<body>
|
||||
|
@ -16,6 +16,11 @@
|
||||
"badge.descriptions.enabled": true,
|
||||
"motto.max.length": 38,
|
||||
"bot.name.max.length": 15,
|
||||
"wired.action.bot.talk.to.avatar.max.length": 64,
|
||||
"wired.action.bot.talk.max.length": 64,
|
||||
"wired.action.chat.max.length": 100,
|
||||
"wired.action.kick.from.room.max.length": 100,
|
||||
"wired.action.mute.user.max.length": 100,
|
||||
"navigator.room.models": [
|
||||
{ "clubLevel": 0, "tileSize": 104, "name": "a" },
|
||||
{ "clubLevel": 0, "tileSize": 94, "name": "b" },
|
||||
@ -120,6 +125,8 @@
|
||||
"catalog.asset.url": "${image.library.url}catalogue",
|
||||
"catalog.asset.image.url": "${catalog.asset.url}/%name%.gif",
|
||||
"catalog.asset.icon.url": "${catalog.asset.url}/icon_%name%.png",
|
||||
"catalog.tab.icons": false,
|
||||
"catalog.headers": false,
|
||||
"chat.input.maxlength": 100,
|
||||
"chat.styles.disabled": [],
|
||||
"chat.styles": [
|
||||
|
@ -1 +1 @@
|
||||
export const GetUIVersion = () => '2.1.0';
|
||||
export const GetUIVersion = () => '2.1.1';
|
||||
|
9
src/api/utils/ConvertSeconds.ts
Normal file
9
src/api/utils/ConvertSeconds.ts
Normal file
@ -0,0 +1,9 @@
|
||||
export const ConvertSeconds = (seconds: number) =>
|
||||
{
|
||||
let numDays = Math.floor(seconds / 86400);
|
||||
let numHours = Math.floor((seconds % 86400) / 3600);
|
||||
let numMinutes = Math.floor(((seconds % 86400) % 3600) / 60);
|
||||
let numSeconds = ((seconds % 86400) % 3600) % 60;
|
||||
|
||||
return numDays.toString().padStart(2, '0') + ':' + numHours.toString().padStart(2, '0') + ':' + numMinutes.toString().padStart(2, '0') + ':' + numSeconds.toString().padStart(2, '0');
|
||||
}
|
1
src/api/utils/GetLocalStorage.ts
Normal file
1
src/api/utils/GetLocalStorage.ts
Normal file
@ -0,0 +1 @@
|
||||
export const GetLocalStorage = <T>(key: string) => JSON.parse(window.localStorage.getItem(key)) as T ?? null;
|
1
src/api/utils/SetLocalStorage.ts
Normal file
1
src/api/utils/SetLocalStorage.ts
Normal file
@ -0,0 +1 @@
|
||||
export const SetLocalStorage = <T>(key: string, value: T) => window.localStorage.setItem(key, JSON.stringify(value));
|
5
src/api/utils/WindowSaveOptions.ts
Normal file
5
src/api/utils/WindowSaveOptions.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export interface WindowSaveOptions
|
||||
{
|
||||
offset: { x: number, y: number };
|
||||
size: { width: number, height: number };
|
||||
}
|
@ -1,5 +1,7 @@
|
||||
export * from './CloneObject';
|
||||
export * from './ColorUtils';
|
||||
export * from './ConvertSeconds';
|
||||
export * from './GetLocalStorage';
|
||||
export * from './LocalizeBadgeDescription';
|
||||
export * from './LocalizeBageName';
|
||||
export * from './LocalizeFormattedNumber';
|
||||
@ -10,4 +12,6 @@ export * from './PlaySound';
|
||||
export * from './ProductImageUtility';
|
||||
export * from './Randomizer';
|
||||
export * from './RoomChatFormatter';
|
||||
export * from './SetLocalStorage';
|
||||
export * from './SoundNames';
|
||||
export * from './WindowSaveOptions';
|
||||
|
BIN
src/assets/images/infostand/countown-timer.png
Normal file
BIN
src/assets/images/infostand/countown-timer.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 219 B |
BIN
src/assets/images/room-widgets/stickie-widget/stickie-dreams.png
Normal file
BIN
src/assets/images/room-widgets/stickie-widget/stickie-dreams.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 26 KiB |
BIN
src/assets/images/room-widgets/stickie-widget/stickie-heart.png
Normal file
BIN
src/assets/images/room-widgets/stickie-widget/stickie-heart.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 945 B |
Binary file not shown.
After Width: | Height: | Size: 28 KiB |
Binary file not shown.
After Width: | Height: | Size: 4.9 KiB |
@ -1,5 +1,6 @@
|
||||
import { FC, useMemo } from 'react';
|
||||
import { FC, useEffect, useMemo, useRef } from 'react';
|
||||
import { Column, ColumnProps } from '..';
|
||||
import { GetLocalStorage, SetLocalStorage, WindowSaveOptions } from '../../api';
|
||||
import { DraggableWindow, DraggableWindowPosition, DraggableWindowProps } from '../draggable-window';
|
||||
import { NitroCardContextProvider } from './NitroCardContext';
|
||||
|
||||
@ -11,6 +12,7 @@ export interface NitroCardViewProps extends DraggableWindowProps, ColumnProps
|
||||
export const NitroCardView: FC<NitroCardViewProps> = props =>
|
||||
{
|
||||
const { theme = 'primary', uniqueKey = null, handleSelector = '.drag-handler', windowPosition = DraggableWindowPosition.CENTER, disableDrag = false, overflow = 'hidden', position = 'relative', gap = 0, classNames = [], ...rest } = props;
|
||||
const elementRef = useRef<HTMLDivElement>();
|
||||
|
||||
const getClassNames = useMemo(() =>
|
||||
{
|
||||
@ -23,10 +25,40 @@ export const NitroCardView: FC<NitroCardViewProps> = props =>
|
||||
return newClassNames;
|
||||
}, [ theme, classNames ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!uniqueKey || !elementRef || !elementRef.current) return;
|
||||
|
||||
const localStorage = GetLocalStorage<WindowSaveOptions>(`nitro.windows.${ uniqueKey }`);
|
||||
const element = elementRef.current;
|
||||
|
||||
if(localStorage && localStorage.size)
|
||||
{
|
||||
element.style.width = `${ localStorage.size.width }px`;
|
||||
element.style.height = `${ localStorage.size.height }px`;
|
||||
}
|
||||
|
||||
const observer = new ResizeObserver(event =>
|
||||
{
|
||||
const newStorage = { ...GetLocalStorage<Partial<WindowSaveOptions>>(`nitro.windows.${ uniqueKey }`) } as WindowSaveOptions;
|
||||
|
||||
newStorage.size = { width: element.offsetWidth, height: element.offsetHeight };
|
||||
|
||||
SetLocalStorage<WindowSaveOptions>(`nitro.windows.${ uniqueKey }`, newStorage);
|
||||
});
|
||||
|
||||
observer.observe(element);
|
||||
|
||||
return () =>
|
||||
{
|
||||
observer.disconnect();
|
||||
}
|
||||
}, [ uniqueKey ]);
|
||||
|
||||
return (
|
||||
<NitroCardContextProvider value={ { theme } }>
|
||||
<DraggableWindow uniqueKey={ uniqueKey } handleSelector={ handleSelector } windowPosition={ windowPosition } disableDrag={ disableDrag }>
|
||||
<Column overflow={ overflow } position={ position } gap={ gap } classNames={ getClassNames } { ...rest } />
|
||||
<Column innerRef={ elementRef } overflow={ overflow } position={ position } gap={ gap } classNames={ getClassNames } { ...rest } />
|
||||
</DraggableWindow>
|
||||
</NitroCardContextProvider>
|
||||
);
|
||||
|
@ -2,10 +2,10 @@ import { MouseEventType, TouchEventType } from '@nitrots/nitro-renderer';
|
||||
import { CSSProperties, FC, Key, MouseEvent as ReactMouseEvent, ReactNode, TouchEvent as ReactTouchEvent, useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { Base } from '..';
|
||||
import { GetLocalStorage, SetLocalStorage, WindowSaveOptions } from '../../api';
|
||||
import { DraggableWindowPosition } from './DraggableWindowPosition';
|
||||
|
||||
const CURRENT_WINDOWS: HTMLElement[] = [];
|
||||
const POS_MEMORY: Map<Key, { x: number, y: number }> = new Map();
|
||||
const BOUNDS_THRESHOLD_TOP: number = 0;
|
||||
const BOUNDS_THRESHOLD_LEFT: number = 0;
|
||||
|
||||
@ -138,7 +138,14 @@ export const DraggableWindow: FC<DraggableWindowProps> = props =>
|
||||
setOffset({ x: offsetX, y: offsetY });
|
||||
setIsDragging(false);
|
||||
|
||||
if(uniqueKey !== null) POS_MEMORY.set(uniqueKey, { x: offsetX, y: offsetY });
|
||||
if(uniqueKey !== null)
|
||||
{
|
||||
const newStorage = { ...GetLocalStorage<WindowSaveOptions>(`nitro.windows.${ uniqueKey }`) } as WindowSaveOptions;
|
||||
|
||||
newStorage.offset = { x: offsetX, y: offsetY };
|
||||
|
||||
SetLocalStorage<WindowSaveOptions>(`nitro.windows.${ uniqueKey }`, newStorage);
|
||||
}
|
||||
}, [ dragHandler, delta, offset, uniqueKey ]);
|
||||
|
||||
const onDragMouseUp = useCallback((event: MouseEvent) =>
|
||||
@ -187,17 +194,6 @@ export const DraggableWindow: FC<DraggableWindowProps> = props =>
|
||||
break;
|
||||
}
|
||||
|
||||
if(uniqueKey !== null)
|
||||
{
|
||||
const memory = POS_MEMORY.get(uniqueKey);
|
||||
|
||||
if(memory)
|
||||
{
|
||||
offsetX = memory.x;
|
||||
offsetY = memory.y;
|
||||
}
|
||||
}
|
||||
|
||||
setDelta({ x: 0, y: 0 });
|
||||
setOffset({ x: offsetX, y: offsetY });
|
||||
|
||||
@ -253,6 +249,18 @@ export const DraggableWindow: FC<DraggableWindowProps> = props =>
|
||||
}
|
||||
}, [ isDragging, onDragMouseUp, onDragMouseMove, onDragTouchUp, onDragTouchMove ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!uniqueKey) return;
|
||||
|
||||
const localStorage = GetLocalStorage<WindowSaveOptions>(`nitro.windows.${ uniqueKey }`);
|
||||
|
||||
if(!localStorage || !localStorage.offset) return;
|
||||
|
||||
setDelta({ x: 0, y: 0 });
|
||||
if(localStorage.offset) setOffset(localStorage.offset);
|
||||
}, [ uniqueKey ]);
|
||||
|
||||
return (
|
||||
createPortal(
|
||||
<Base position="absolute" innerRef={ elementRef } className="draggable-window" onMouseDownCapture={ onMouseDown } onTouchStartCapture={ onTouchStart } style={ dragStyle }>
|
||||
|
@ -5,10 +5,10 @@
|
||||
background-color: $grid-bg-color;
|
||||
|
||||
&.active {
|
||||
border-color: $grid-active-border-color !important;
|
||||
border-color: $grid-active-border-color !important;
|
||||
|
||||
&:not(.clear-bg) {
|
||||
background-color: $grid-active-bg-color !important;
|
||||
background-color: $grid-active-bg-color !important;
|
||||
}
|
||||
}
|
||||
|
||||
@ -53,7 +53,7 @@
|
||||
position: absolute;
|
||||
width: 110px;
|
||||
height: 110px;
|
||||
margin-top: 38px;
|
||||
margin-top: 30px;
|
||||
margin-left: 3px;
|
||||
}
|
||||
}
|
||||
@ -110,8 +110,7 @@
|
||||
.gift-incognito {
|
||||
width: 37px;
|
||||
height: 48px;
|
||||
background: url("../assets/images/gift/incognito.png") center
|
||||
no-repeat;
|
||||
background: url("../assets/images/gift/incognito.png") center no-repeat;
|
||||
}
|
||||
|
||||
.gift-avatar {
|
||||
@ -168,23 +167,27 @@
|
||||
}
|
||||
|
||||
@-webkit-keyframes sk-bouncedelay {
|
||||
|
||||
0%,
|
||||
80%,
|
||||
100% {
|
||||
-webkit-transform: scale(0);
|
||||
}
|
||||
|
||||
40% {
|
||||
-webkit-transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes sk-bouncedelay {
|
||||
|
||||
0%,
|
||||
80%,
|
||||
100% {
|
||||
-webkit-transform: scale(0);
|
||||
transform: scale(0);
|
||||
}
|
||||
|
||||
40% {
|
||||
-webkit-transform: scale(1);
|
||||
transform: scale(1);
|
||||
@ -195,8 +198,7 @@
|
||||
position: relative;
|
||||
width: 110px;
|
||||
height: 110px;
|
||||
background: url("../assets/images/navigator/thumbnail_placeholder.png")
|
||||
no-repeat center;
|
||||
background: url("../assets/images/navigator/thumbnail_placeholder.png") no-repeat center;
|
||||
background-color: rgba($black, 0.125);
|
||||
}
|
||||
|
||||
@ -299,6 +301,19 @@
|
||||
}
|
||||
}
|
||||
|
||||
.nitro-counter-time {
|
||||
width: 36px;
|
||||
height: 28px;
|
||||
background: url("../assets/images/infostand/countown-timer.png");
|
||||
|
||||
div {
|
||||
line-height: 28px;
|
||||
text-align: center;
|
||||
color: $white;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.avatar-image {
|
||||
position: relative;
|
||||
width: 90px;
|
||||
@ -375,15 +390,13 @@
|
||||
content: "";
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: url("../assets/images/unique/grid-bg-glass.png") center
|
||||
no-repeat;
|
||||
background: url("../assets/images/unique/grid-bg-glass.png") center no-repeat;
|
||||
bottom: 0;
|
||||
z-index: 4;
|
||||
}
|
||||
|
||||
&.sold-out:after {
|
||||
background: url("../assets/images/unique/grid-bg-sold-out.png") center
|
||||
no-repeat,
|
||||
background: url("../assets/images/unique/grid-bg-sold-out.png") center no-repeat,
|
||||
url("../assets/images/unique/grid-bg-glass.png") center no-repeat;
|
||||
}
|
||||
|
||||
@ -395,8 +408,7 @@
|
||||
bottom: 1px;
|
||||
width: 100%;
|
||||
height: 9px;
|
||||
background: url("../assets/images/unique/grid-count-bg.png") center
|
||||
no-repeat;
|
||||
background: url("../assets/images/unique/grid-count-bg.png") center no-repeat;
|
||||
z-index: 3;
|
||||
}
|
||||
}
|
||||
@ -438,8 +450,7 @@
|
||||
.unique-complete-plate {
|
||||
width: 170px;
|
||||
height: 29px;
|
||||
background: url("../assets/images/unique/catalog-info-amount-bg.png")
|
||||
no-repeat center;
|
||||
background: url("../assets/images/unique/catalog-info-amount-bg.png") no-repeat center;
|
||||
z-index: 1;
|
||||
padding-top: 3px;
|
||||
|
||||
@ -535,12 +546,10 @@
|
||||
z-index: 1;
|
||||
transition: all 1s;
|
||||
border-radius: calc(#{$border-radius} / 2);
|
||||
background: repeating-linear-gradient(
|
||||
$tertiary,
|
||||
$tertiary 50%,
|
||||
$quaternary 50%,
|
||||
$quaternary 100%
|
||||
);
|
||||
background: repeating-linear-gradient($tertiary,
|
||||
$tertiary 50%,
|
||||
$quaternary 50%,
|
||||
$quaternary 100%);
|
||||
}
|
||||
|
||||
.nitro-progress-bar-text {
|
||||
|
43
src/common/layout/LayoutCounterTimeView.tsx
Normal file
43
src/common/layout/LayoutCounterTimeView.tsx
Normal file
@ -0,0 +1,43 @@
|
||||
import { FC, useMemo } from 'react';
|
||||
import { LocalizeText } from '../../api';
|
||||
import { Base, BaseProps } from '../Base';
|
||||
import { Flex } from '../Flex';
|
||||
|
||||
interface LayoutCounterTimeViewProps extends BaseProps<HTMLDivElement>
|
||||
{
|
||||
day: string;
|
||||
hour: string;
|
||||
minutes: string;
|
||||
seconds: string;
|
||||
}
|
||||
|
||||
export const LayoutCounterTimeView: FC<LayoutCounterTimeViewProps> = props =>
|
||||
{
|
||||
const { day = '00', hour = '00', minutes = '00', seconds = '00', classNames = [], children = null, ...rest } = props;
|
||||
|
||||
const getClassNames = useMemo(() =>
|
||||
{
|
||||
const newClassNames: string[] = [ 'nitro-counter-time' ];
|
||||
|
||||
if(classNames.length) newClassNames.push(...classNames);
|
||||
|
||||
return newClassNames;
|
||||
}, [ classNames ]);
|
||||
|
||||
return (
|
||||
<Flex gap={ 1 }>
|
||||
<Base classNames={ getClassNames } { ...rest }>
|
||||
<div>{ day != '00' ? day : hour }{ day != '00' ? LocalizeText('countdown_clock_unit_days') : LocalizeText('countdown_clock_unit_hours') }</div>
|
||||
</Base>
|
||||
<Base style={ { marginTop: '3px' } }>:</Base>
|
||||
<Base classNames={ getClassNames } { ...rest }>
|
||||
<div>{ minutes }{ LocalizeText('countdown_clock_unit_minutes') }</div>
|
||||
</Base>
|
||||
<Base style={ { marginTop: '3px' } }>:</Base>
|
||||
<Base classNames={ getClassNames } { ...rest }>
|
||||
<div>{ seconds }{ LocalizeText('countdown_clock_unit_seconds') }</div>
|
||||
</Base>
|
||||
{ children }
|
||||
</Flex>
|
||||
);
|
||||
}
|
@ -20,6 +20,8 @@ export const LayoutPetImageView: FC<LayoutPetImageViewProps> = props =>
|
||||
{
|
||||
const { figure = '', typeId = -1, paletteId = -1, petColor = 0xFFFFFF, customParts = [], posture = 'std', headOnly = false, direction = 0, scale = 1, style = {}, ...rest } = props;
|
||||
const [ petUrl, setPetUrl ] = useState<string>(null);
|
||||
const [ width, setWidth ] = useState(0);
|
||||
const [ height, setHeight ] = useState(0);
|
||||
const isDisposed = useRef(false);
|
||||
|
||||
const getStyle = useMemo(() =>
|
||||
@ -35,10 +37,13 @@ export const LayoutPetImageView: FC<LayoutPetImageViewProps> = props =>
|
||||
if(!(scale % 1)) newStyle.imageRendering = 'pixelated';
|
||||
}
|
||||
|
||||
newStyle.width = width;
|
||||
newStyle.height = height;
|
||||
|
||||
if(Object.keys(style).length) newStyle = { ...newStyle, ...style };
|
||||
|
||||
return newStyle;
|
||||
}, [ petUrl, scale, style ]);
|
||||
}, [ petUrl, scale, style, width, height ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
@ -67,8 +72,19 @@ export const LayoutPetImageView: FC<LayoutPetImageViewProps> = props =>
|
||||
{
|
||||
if(isDisposed.current) return;
|
||||
|
||||
if(image) setPetUrl(image.src);
|
||||
else if(texture) setPetUrl(TextureUtils.generateImageUrl(texture));
|
||||
if(image)
|
||||
{
|
||||
setPetUrl(image.src);
|
||||
setWidth(image.width);
|
||||
setHeight(image.height);
|
||||
}
|
||||
|
||||
else if(texture)
|
||||
{
|
||||
setPetUrl(TextureUtils.generateImageUrl(texture));
|
||||
setWidth(texture.width);
|
||||
setHeight(texture.height);
|
||||
}
|
||||
},
|
||||
imageFailed: (id) =>
|
||||
{
|
||||
@ -80,9 +96,12 @@ export const LayoutPetImageView: FC<LayoutPetImageViewProps> = props =>
|
||||
{
|
||||
const image = imageResult.getImage();
|
||||
|
||||
if(image) setPetUrl(image.src);
|
||||
|
||||
|
||||
if(image)
|
||||
{
|
||||
setPetUrl(image.src);
|
||||
setWidth(image.width);
|
||||
setHeight(image.height);
|
||||
}
|
||||
}
|
||||
}, [ figure, typeId, paletteId, petColor, customParts, posture, headOnly, direction ]);
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
export * from './LayoutAvatarImageView';
|
||||
export * from './LayoutBackgroundImage';
|
||||
export * from './LayoutBadgeImageView';
|
||||
export * from './LayoutCounterTimeView';
|
||||
export * from './LayoutCurrencyIcon';
|
||||
export * from './LayoutFurniIconImageView';
|
||||
export * from './LayoutFurniImageView';
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { FC, useCallback, useEffect, useState } from 'react';
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import { AvatarEditorGridPartItem, GetConfiguration } from '../../../../api';
|
||||
import { LayoutCurrencyIcon, LayoutGridItem, LayoutGridItemProps } from '../../../../common';
|
||||
import { AvatarEditorIcon } from '../AvatarEditorIcon';
|
||||
@ -15,20 +15,14 @@ export const AvatarEditorFigureSetItemView: FC<AvatarEditorFigureSetItemViewProp
|
||||
|
||||
const hcDisabled = GetConfiguration<boolean>('hc.disabled', false);
|
||||
|
||||
const rerender = useCallback(() =>
|
||||
{
|
||||
setUpdateId(prevValue => (prevValue + 1));
|
||||
}, []);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
const rerender = () => setUpdateId(prevValue => (prevValue + 1));
|
||||
|
||||
partItem.notify = rerender;
|
||||
|
||||
return () =>
|
||||
{
|
||||
partItem.notify = null;
|
||||
}
|
||||
}, [ partItem, rerender ]);
|
||||
return () => partItem.notify = null;
|
||||
}, [ partItem ]);
|
||||
|
||||
return (
|
||||
<LayoutGridItem itemImage={ (partItem.isClear ? undefined : partItem.imageUrl) } itemActive={ partItem.isSelected } { ...rest }>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Dispatch, FC, SetStateAction, useCallback } from 'react';
|
||||
import { Dispatch, FC, SetStateAction, useCallback, useEffect, useRef } from 'react';
|
||||
import { AvatarEditorGridPartItem, CategoryData, IAvatarEditorCategoryModel } from '../../../../api';
|
||||
import { AutoGrid } from '../../../../common';
|
||||
import { AvatarEditorFigureSetItemView } from './AvatarEditorFigureSetItemView';
|
||||
@ -13,6 +13,7 @@ export interface AvatarEditorFigureSetViewProps
|
||||
export const AvatarEditorFigureSetView: FC<AvatarEditorFigureSetViewProps> = props =>
|
||||
{
|
||||
const { model = null, category = null, setMaxPaletteCount = null } = props;
|
||||
const elementRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const selectPart = useCallback((item: AvatarEditorGridPartItem) =>
|
||||
{
|
||||
@ -27,8 +28,15 @@ export const AvatarEditorFigureSetView: FC<AvatarEditorFigureSetViewProps> = pro
|
||||
setMaxPaletteCount(partItem.maxColorIndex || 1);
|
||||
}, [ model, category, setMaxPaletteCount ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!model || !category || !elementRef || !elementRef.current) return;
|
||||
|
||||
elementRef.current.scrollTop = 0;
|
||||
}, [ model, category ]);
|
||||
|
||||
return (
|
||||
<AutoGrid columnCount={ 3 } columnMinHeight={ 50 }>
|
||||
<AutoGrid innerRef={ elementRef } columnCount={ 3 } columnMinHeight={ 50 }>
|
||||
{ (category.parts.length > 0) && category.parts.map((item, index) =>
|
||||
<AvatarEditorFigureSetItemView key={ index } partItem={ item } onClick={ event => selectPart(item) } />) }
|
||||
</AutoGrid>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { FC, useCallback, useEffect, useState } from 'react';
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import { AvatarEditorGridColorItem, GetConfiguration } from '../../../../api';
|
||||
import { LayoutCurrencyIcon, LayoutGridItem, LayoutGridItemProps } from '../../../../common';
|
||||
|
||||
@ -14,17 +14,14 @@ export const AvatarEditorPaletteSetItem: FC<AvatarEditorPaletteSetItemProps> = p
|
||||
|
||||
const hcDisabled = GetConfiguration<boolean>('hc.disabled', false);
|
||||
|
||||
const rerender = useCallback(() =>
|
||||
{
|
||||
setUpdateId(prevValue => (prevValue + 1));
|
||||
}, []);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
const rerender = () => setUpdateId(prevValue => (prevValue + 1));
|
||||
|
||||
colorItem.notify = rerender;
|
||||
|
||||
return () => colorItem.notify = null;
|
||||
});
|
||||
}, [ colorItem ]);
|
||||
|
||||
return (
|
||||
<LayoutGridItem itemHighlight itemColor={ colorItem.color } itemActive={ colorItem.isSelected } className="clear-bg" { ...rest }>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { FC, useCallback } from 'react';
|
||||
import { FC, useCallback, useEffect, useRef } from 'react';
|
||||
import { AvatarEditorGridColorItem, CategoryData, IAvatarEditorCategoryModel } from '../../../../api';
|
||||
import { AutoGrid } from '../../../../common';
|
||||
import { AvatarEditorPaletteSetItem } from './AvatarEditorPaletteSetItemView';
|
||||
@ -14,6 +14,7 @@ export interface AvatarEditorPaletteSetViewProps
|
||||
export const AvatarEditorPaletteSetView: FC<AvatarEditorPaletteSetViewProps> = props =>
|
||||
{
|
||||
const { model = null, category = null, paletteSet = [], paletteIndex = -1 } = props;
|
||||
const elementRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const selectColor = useCallback((item: AvatarEditorGridColorItem) =>
|
||||
{
|
||||
@ -24,8 +25,15 @@ export const AvatarEditorPaletteSetView: FC<AvatarEditorPaletteSetViewProps> = p
|
||||
model.selectColor(category.name, index, paletteIndex);
|
||||
}, [ model, category, paletteSet, paletteIndex ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!model || !category || !elementRef || !elementRef.current) return;
|
||||
|
||||
elementRef.current.scrollTop = 0;
|
||||
}, [ model, category ]);
|
||||
|
||||
return (
|
||||
<AutoGrid gap={ 1 } columnCount={ 5 } columnMinWidth={ 30 }>
|
||||
<AutoGrid innerRef={ elementRef } gap={ 1 } columnCount={ 5 } columnMinWidth={ 30 }>
|
||||
{ (paletteSet.length > 0) && paletteSet.map((item, index) =>
|
||||
<AvatarEditorPaletteSetItem key={ index } colorItem={ item } onClick={ event => selectColor(item) } />) }
|
||||
</AutoGrid>
|
||||
|
@ -2,7 +2,7 @@
|
||||
width: $catalog-width;
|
||||
height: $catalog-height;
|
||||
|
||||
font[size='16'] {
|
||||
font[size="16"] {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
@ -22,7 +22,7 @@
|
||||
|
||||
.nitro-catalog-gift {
|
||||
width: 325px;
|
||||
|
||||
|
||||
.gift-preview {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
@ -37,15 +37,24 @@
|
||||
}
|
||||
|
||||
.nitro-catalog-navigation-grid-container {
|
||||
border-color: #B6BEC5 !important;
|
||||
background-color: #CDD3D9;
|
||||
border-color: #b6bec5 !important;
|
||||
background-color: #cdd3d9;
|
||||
border: 2px solid;
|
||||
|
||||
.nitro-catalog-navigation-section {
|
||||
display: grid;
|
||||
|
||||
.nitro-catalog-navigation-section {
|
||||
padding-left: 5px;
|
||||
border-left: 2px solid #b6bec5;
|
||||
}
|
||||
}
|
||||
|
||||
.layout-grid-item {
|
||||
font-size: $font-size-sm;
|
||||
height: 23px !important;
|
||||
border-color: unset !important;
|
||||
background-color: #CDD3D9;
|
||||
background-color: #cdd3d9;
|
||||
border: 0 !important;
|
||||
padding: 1px 3px;
|
||||
|
||||
@ -58,23 +67,21 @@
|
||||
}
|
||||
|
||||
.nitro-catalog-layout-info-loyalty {
|
||||
|
||||
.info-loyalty-content {
|
||||
background-repeat: no-repeat;
|
||||
background-position: top right;
|
||||
background-image: url('../../assets/images/catalog/diamond_info_illustration.gif');
|
||||
padding-right:123px;
|
||||
background-image: url("../../assets/images/catalog/diamond_info_illustration.gif");
|
||||
padding-right: 123px;
|
||||
}
|
||||
|
||||
.info-image {
|
||||
width: 123px;
|
||||
height:350px;
|
||||
background-image: url('../../assets/images/catalog/diamond_info_illustration.gif');
|
||||
height: 350px;
|
||||
background-image: url("../../assets/images/catalog/diamond_info_illustration.gif");
|
||||
}
|
||||
}
|
||||
|
||||
.nitro-catalog-layout-vip-buy-grid {
|
||||
|
||||
.layout-grid-item {
|
||||
height: 50px !important;
|
||||
max-height: 50px !important;
|
||||
@ -82,20 +89,18 @@
|
||||
.icon-hc-banner {
|
||||
width: 68px;
|
||||
height: 40px;
|
||||
background: url('../../assets/images/catalog/hc_big.png') center no-repeat;
|
||||
background: url("../../assets/images/catalog/hc_big.png") center no-repeat;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nitro-catalog-layout-marketplace-grid {
|
||||
|
||||
.layout-grid-item {
|
||||
height: 75px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.nitro-catalog-layout-vip-gifts-grid {
|
||||
|
||||
.layout-grid-item {
|
||||
height: 55px !important;
|
||||
max-height: 55px !important;
|
||||
@ -108,8 +113,12 @@
|
||||
}
|
||||
|
||||
.nitro-catalog-layout-bundle-grid {
|
||||
|
||||
.layout-grid-item {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.nitro-catalog-header {
|
||||
width: 290px;
|
||||
height: 60px;
|
||||
}
|
||||
|
@ -1,8 +1,9 @@
|
||||
import { ILinkEventTracker } from '@nitrots/nitro-renderer';
|
||||
import { FC, useEffect } from 'react';
|
||||
import { AddEventLinkTracker, LocalizeText, RemoveLinkEventTracker } from '../../api';
|
||||
import { Column, Grid, NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView } from '../../common';
|
||||
import { AddEventLinkTracker, GetConfiguration, LocalizeText, RemoveLinkEventTracker } from '../../api';
|
||||
import { Column, Flex, Grid, NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView } from '../../common';
|
||||
import { useCatalog } from '../../hooks';
|
||||
import { CatalogIconView } from './views/catalog-icon/CatalogIconView';
|
||||
import { CatalogGiftView } from './views/gift/CatalogGiftView';
|
||||
import { CatalogNavigationView } from './views/navigation/CatalogNavigationView';
|
||||
import { GetCatalogLayout } from './views/page/layout/GetCatalogLayout';
|
||||
@ -10,7 +11,7 @@ import { MarketplacePostOfferView } from './views/page/layout/marketplace/Market
|
||||
|
||||
export const CatalogView: FC<{}> = props =>
|
||||
{
|
||||
const { isVisible = false, setIsVisible = null, rootNode = null, currentPage = null, navigationHidden = false, setNavigationHidden = null, activeNodes = [], searchResult = null, setSearchResult = null, openPageByName = null, openPageByOfferId = null, activateNode = null } = useCatalog();
|
||||
const { isVisible = false, setIsVisible = null, rootNode = null, currentPage = null, navigationHidden = false, setNavigationHidden = null, activeNodes = [], searchResult = null, setSearchResult = null, openPageByName = null, openPageByOfferId = null, activateNode = null, getNodeById } = useCatalog();
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
@ -68,7 +69,7 @@ export const CatalogView: FC<{}> = props =>
|
||||
return (
|
||||
<>
|
||||
{ isVisible &&
|
||||
<NitroCardView uniqueKey="catalog" className="nitro-catalog">
|
||||
<NitroCardView uniqueKey="catalog" className="nitro-catalog" style={ { width: GetConfiguration('catalog.headers') ? '710px' : '' } }>
|
||||
<NitroCardHeaderView headerText={ LocalizeText('catalog.title') } onCloseClick={ event => setIsVisible(false) } />
|
||||
<NitroCardTabsView>
|
||||
{ rootNode && (rootNode.children.length > 0) && rootNode.children.map(child =>
|
||||
@ -81,8 +82,11 @@ export const CatalogView: FC<{}> = props =>
|
||||
if(searchResult) setSearchResult(null);
|
||||
|
||||
activateNode(child);
|
||||
} }>
|
||||
{ child.localization }
|
||||
} } >
|
||||
<Flex gap={ GetConfiguration('catalog.tab.icons') ? 1 : 0 } alignItems="center">
|
||||
{ GetConfiguration('catalog.tab.icons') && <CatalogIconView icon={ child.iconId } /> }
|
||||
{ child.localization }
|
||||
</Flex>
|
||||
</NitroCardTabsItemView>
|
||||
);
|
||||
}) }
|
||||
|
@ -0,0 +1,26 @@
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import { GetConfiguration } from '../../../../api';
|
||||
import { Flex } from '../../../../common';
|
||||
|
||||
export interface CatalogHeaderViewProps
|
||||
{
|
||||
imageUrl?: string;
|
||||
}
|
||||
|
||||
export const CatalogHeaderView: FC<CatalogHeaderViewProps> = props =>
|
||||
{
|
||||
const { imageUrl = null } = props;
|
||||
const [ displayImageUrl, setDisplayImageUrl ] = useState('');
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
setDisplayImageUrl(imageUrl ?? GetConfiguration<string>('catalog.asset.image.url').replace('%name%', 'catalog_header_roombuilder'));
|
||||
}, [ imageUrl ]);
|
||||
|
||||
return <Flex center fullWidth className="nitro-catalog-header">
|
||||
<img src={ displayImageUrl } onError={ ({ currentTarget }) =>
|
||||
{
|
||||
currentTarget.src = GetConfiguration<string>('catalog.asset.image.url').replace('%name%', 'catalog_header_roombuilder');
|
||||
} } />
|
||||
</Flex>;
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { FC } from 'react';
|
||||
import { ICatalogNode } from '../../../../api';
|
||||
import { LayoutGridItem, Text } from '../../../../common';
|
||||
import { Base, LayoutGridItem, Text } from '../../../../common';
|
||||
import { useCatalog } from '../../../../hooks';
|
||||
import { CatalogIconView } from '../catalog-icon/CatalogIconView';
|
||||
import { CatalogNavigationSetView } from './CatalogNavigationSetView';
|
||||
@ -9,23 +9,24 @@ import { CatalogNavigationSetView } from './CatalogNavigationSetView';
|
||||
export interface CatalogNavigationItemViewProps
|
||||
{
|
||||
node: ICatalogNode;
|
||||
child?: boolean;
|
||||
}
|
||||
|
||||
export const CatalogNavigationItemView: FC<CatalogNavigationItemViewProps> = props =>
|
||||
{
|
||||
const { node = null } = props;
|
||||
const { node = null, child = false } = props;
|
||||
const { activateNode = null } = useCatalog();
|
||||
|
||||
return (
|
||||
<>
|
||||
<LayoutGridItem gap={ 1 } column={ false } itemActive={ node.isActive } onClick={ event => activateNode(node) }>
|
||||
<Base className="nitro-catalog-navigation-section">
|
||||
<LayoutGridItem gap={ 1 } column={ false } itemActive={ node.isActive } onClick={ event => activateNode(node) } className={ child ? 'inset' : '' }>
|
||||
<CatalogIconView icon={ node.iconId } />
|
||||
<Text grow truncate>{ node.localization }</Text>
|
||||
{ node.isBranch &&
|
||||
<FontAwesomeIcon icon={ node.isOpen ? 'caret-up' : 'caret-down' } /> }
|
||||
</LayoutGridItem>
|
||||
{ node.isOpen && node.isBranch &&
|
||||
<CatalogNavigationSetView node={ node } /> }
|
||||
</>
|
||||
<CatalogNavigationSetView node={ node } child={ true } /> }
|
||||
</Base>
|
||||
);
|
||||
}
|
||||
|
@ -5,11 +5,12 @@ import { CatalogNavigationItemView } from './CatalogNavigationItemView';
|
||||
export interface CatalogNavigationSetViewProps
|
||||
{
|
||||
node: ICatalogNode;
|
||||
child?: boolean;
|
||||
}
|
||||
|
||||
export const CatalogNavigationSetView: FC<CatalogNavigationSetViewProps> = props =>
|
||||
{
|
||||
const { node = null } = props;
|
||||
const { node = null, child = false } = props;
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -17,7 +18,7 @@ export const CatalogNavigationSetView: FC<CatalogNavigationSetViewProps> = props
|
||||
{
|
||||
if(!n.isVisible) return null;
|
||||
|
||||
return <CatalogNavigationItemView key={ index } node={ n } />
|
||||
return <CatalogNavigationItemView key={ index } node={ n } child={ child } />
|
||||
}) }
|
||||
</>
|
||||
);
|
||||
|
@ -20,7 +20,7 @@ export const CatalogNavigationView: FC<CatalogNavigationViewProps> = props =>
|
||||
<>
|
||||
<CatalogSearchView />
|
||||
<Column fullHeight className="nitro-catalog-navigation-grid-container rounded p-1" overflow="hidden">
|
||||
<AutoGrid gap={ 1 } columnCount={ 1 }>
|
||||
<AutoGrid id="nitro-catalog-main-navigation" gap={ 1 } columnCount={ 1 }>
|
||||
{ searchResult && (searchResult.filteredNodes.length > 0) && searchResult.filteredNodes.map((n, index) =>
|
||||
{
|
||||
return <CatalogNavigationItemView key={ index } node={ n } />;
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { FC } from 'react';
|
||||
import { ProductTypeEnum } from '../../../../../api';
|
||||
import { Column, Flex, Grid, Text } from '../../../../../common';
|
||||
import { GetConfiguration, ProductTypeEnum } from '../../../../../api';
|
||||
import { Column, Flex, Grid, LayoutImage, Text } from '../../../../../common';
|
||||
import { useCatalog } from '../../../../../hooks';
|
||||
import { CatalogHeaderView } from '../../catalog-header/CatalogHeaderView';
|
||||
import { CatalogAddOnBadgeWidgetView } from '../widgets/CatalogAddOnBadgeWidgetView';
|
||||
import { CatalogItemGridWidgetView } from '../widgets/CatalogItemGridWidgetView';
|
||||
import { CatalogLimitedItemWidgetView } from '../widgets/CatalogLimitedItemWidgetView';
|
||||
@ -14,42 +15,47 @@ import { CatalogLayoutProps } from './CatalogLayout.types';
|
||||
export const CatalogLayoutDefaultView: FC<CatalogLayoutProps> = props =>
|
||||
{
|
||||
const { page = null } = props;
|
||||
const { currentOffer = null } = useCatalog();
|
||||
const { currentOffer = null, currentPage = null } = useCatalog();
|
||||
|
||||
return (
|
||||
<Grid>
|
||||
<Column size={ 7 } overflow="hidden">
|
||||
<CatalogItemGridWidgetView />
|
||||
</Column>
|
||||
<Column center={ !currentOffer } size={ 5 } overflow="hidden">
|
||||
{ !currentOffer &&
|
||||
<>
|
||||
{ !!page.localization.getImage(1) && <img alt="" src={ page.localization.getImage(1) } /> }
|
||||
<Text center dangerouslySetInnerHTML={ { __html: page.localization.getText(0) } } />
|
||||
</> }
|
||||
{ currentOffer &&
|
||||
<>
|
||||
<Flex center overflow="hidden" style={ { height: 140 } }>
|
||||
{ (currentOffer.product.productType !== ProductTypeEnum.BADGE) &&
|
||||
<>
|
||||
<CatalogViewProductWidgetView />
|
||||
<CatalogLimitedItemWidgetView fullWidth position="absolute" className="top-1" />
|
||||
<CatalogAddOnBadgeWidgetView className="bg-muted rounded bottom-1 end-1" />
|
||||
</> }
|
||||
{ (currentOffer.product.productType === ProductTypeEnum.BADGE) && <CatalogAddOnBadgeWidgetView className="scale-2" /> }
|
||||
</Flex>
|
||||
<Column grow gap={ 1 }>
|
||||
<Text grow truncate>{ currentOffer.localizationName }</Text>
|
||||
<Flex justifyContent="between">
|
||||
<Column gap={ 1 }>
|
||||
<CatalogSpinnerWidgetView />
|
||||
</Column>
|
||||
<CatalogTotalPriceWidget justifyContent="end" alignItems="end" />
|
||||
<>
|
||||
<Grid>
|
||||
<Column size={ 7 } overflow="hidden">
|
||||
{ GetConfiguration('catalog.headers') &&
|
||||
<CatalogHeaderView imageUrl={ currentPage.localization.getImage(0) }/> }
|
||||
<CatalogItemGridWidgetView />
|
||||
</Column>
|
||||
<Column center={ !currentOffer } size={ 5 } overflow="hidden">
|
||||
{ !currentOffer &&
|
||||
<>
|
||||
{ !!page.localization.getImage(1) &&
|
||||
<LayoutImage imageUrl={ page.localization.getImage(1) } /> }
|
||||
<Text center dangerouslySetInnerHTML={ { __html: page.localization.getText(0) } } />
|
||||
</> }
|
||||
{ currentOffer &&
|
||||
<>
|
||||
<Flex center overflow="hidden" style={ { height: 140 } }>
|
||||
{ (currentOffer.product.productType !== ProductTypeEnum.BADGE) &&
|
||||
<>
|
||||
<CatalogViewProductWidgetView />
|
||||
<CatalogLimitedItemWidgetView fullWidth position="absolute" className="top-1" />
|
||||
<CatalogAddOnBadgeWidgetView className="bg-muted rounded bottom-1 end-1" />
|
||||
</> }
|
||||
{ (currentOffer.product.productType === ProductTypeEnum.BADGE) && <CatalogAddOnBadgeWidgetView className="scale-2" /> }
|
||||
</Flex>
|
||||
<CatalogPurchaseWidgetView />
|
||||
</Column>
|
||||
</> }
|
||||
</Column>
|
||||
</Grid>
|
||||
<Column grow gap={ 1 }>
|
||||
<Text grow truncate>{ currentOffer.localizationName }</Text>
|
||||
<Flex justifyContent="between">
|
||||
<Column gap={ 1 }>
|
||||
<CatalogSpinnerWidgetView />
|
||||
</Column>
|
||||
<CatalogTotalPriceWidget justifyContent="end" alignItems="end" />
|
||||
</Flex>
|
||||
<CatalogPurchaseWidgetView />
|
||||
</Column>
|
||||
</> }
|
||||
</Column>
|
||||
</Grid>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -9,10 +9,22 @@ export const MarketplacePostOfferView : FC<{}> = props =>
|
||||
{
|
||||
const [ item, setItem ] = useState<FurnitureItem>(null);
|
||||
const [ askingPrice, setAskingPrice ] = useState(0);
|
||||
const [ tempAskingPrice, setTempAskingPrice ] = useState('0');
|
||||
const { catalogOptions = null, setCatalogOptions = null } = useCatalog();
|
||||
const { marketplaceConfiguration = null } = catalogOptions;
|
||||
const { showConfirm = null } = useNotification();
|
||||
|
||||
const updateAskingPrice = (price: string) =>
|
||||
{
|
||||
setTempAskingPrice(price);
|
||||
|
||||
const newValue = parseInt(price);
|
||||
|
||||
if(isNaN(newValue) || (newValue === askingPrice)) return;
|
||||
|
||||
setAskingPrice(parseInt(price));
|
||||
}
|
||||
|
||||
useMessageEvent<MarketplaceConfigurationEvent>(MarketplaceConfigurationEvent, event =>
|
||||
{
|
||||
const parser = event.getParser();
|
||||
@ -64,7 +76,7 @@ export const MarketplacePostOfferView : FC<{}> = props =>
|
||||
setItem(null)
|
||||
}, null, null, LocalizeText('inventory.marketplace.confirm_offer.title'));
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<NitroCardView className="nitro-catalog-layout-marketplace-post-offer" theme="primary-slim">
|
||||
<NitroCardHeaderView headerText={ LocalizeText('inventory.marketplace.make_offer.title') } onCloseClick={ event => setItem(null) } />
|
||||
@ -83,7 +95,7 @@ export const MarketplacePostOfferView : FC<{}> = props =>
|
||||
{ LocalizeText('inventory.marketplace.make_offer.expiration_info', [ 'time' ], [ marketplaceConfiguration.offerTime.toString() ]) }
|
||||
</Text>
|
||||
<div className="input-group has-validation">
|
||||
<input className="form-control form-control-sm" type="number" min={ 0 } value={ askingPrice } onChange={ event => setAskingPrice(parseInt(event.target.value)) } placeholder={ LocalizeText('inventory.marketplace.make_offer.price_request') } />
|
||||
<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') } />
|
||||
{ ((askingPrice < marketplaceConfiguration.minimumPrice) || isNaN(askingPrice)) &&
|
||||
<Base className="invalid-feedback d-block">
|
||||
{ LocalizeText('inventory.marketplace.make_offer.min_price', [ 'minprice' ], [ marketplaceConfiguration.minimumPrice.toString() ]) }
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { FC } from 'react';
|
||||
import { FC, useEffect, useRef } from 'react';
|
||||
import { AutoGrid, AutoGridProps, LayoutGridItem } from '../../../../../common';
|
||||
import { useCatalog } from '../../../../../hooks';
|
||||
|
||||
@ -11,11 +11,17 @@ export const CatalogBundleGridWidgetView: FC<CatalogBundleGridWidgetViewProps> =
|
||||
{
|
||||
const { columnCount = 5, children = null, ...rest } = props;
|
||||
const { currentOffer = null } = useCatalog();
|
||||
const elementRef = useRef<HTMLDivElement>();
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(elementRef && elementRef.current) elementRef.current.scrollTop = 0;
|
||||
}, [ currentOffer ]);
|
||||
|
||||
if(!currentOffer) return null;
|
||||
|
||||
return (
|
||||
<AutoGrid columnCount={ 5 } { ...rest }>
|
||||
<AutoGrid innerRef={ elementRef } columnCount={ 5 } { ...rest }>
|
||||
{ currentOffer.products && (currentOffer.products.length > 0) && currentOffer.products.map((product, index) => <LayoutGridItem key={ index } itemImage={ product.getIconUrl() } itemCount={ product.productCount } />) }
|
||||
{ children }
|
||||
</AutoGrid>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { FC } from 'react';
|
||||
import { FC, useEffect, useRef } from 'react';
|
||||
import { IPurchasableOffer, ProductTypeEnum } from '../../../../../api';
|
||||
import { AutoGrid, AutoGridProps } from '../../../../../common';
|
||||
import { useCatalog } from '../../../../../hooks';
|
||||
@ -13,6 +13,12 @@ export const CatalogItemGridWidgetView: FC<CatalogItemGridWidgetViewProps> = pro
|
||||
{
|
||||
const { columnCount = 5, children = null, ...rest } = props;
|
||||
const { currentOffer = null, setCurrentOffer = null, currentPage = null, setPurchaseOptions = null } = useCatalog();
|
||||
const elementRef = useRef<HTMLDivElement>();
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(elementRef && elementRef.current) elementRef.current.scrollTop = 0;
|
||||
}, [ currentPage ]);
|
||||
|
||||
if(!currentPage) return null;
|
||||
|
||||
@ -38,7 +44,7 @@ export const CatalogItemGridWidgetView: FC<CatalogItemGridWidgetViewProps> = pro
|
||||
}
|
||||
|
||||
return (
|
||||
<AutoGrid columnCount={ columnCount } { ...rest }>
|
||||
<AutoGrid innerRef={ elementRef } columnCount={ columnCount } { ...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 } />) }
|
||||
{ children }
|
||||
</AutoGrid>
|
||||
|
@ -103,11 +103,7 @@ export const CatalogPurchaseWidgetView: FC<CatalogPurchaseWidgetViewProps> = pro
|
||||
{
|
||||
if(!currentOffer) return;
|
||||
|
||||
return () =>
|
||||
{
|
||||
setPurchaseState(CatalogPurchaseState.NONE);
|
||||
setPurchaseOptions({ quantity: 1, extraData: null, extraParamRequired: false, previewStuffData: null });
|
||||
}
|
||||
setPurchaseState(CatalogPurchaseState.NONE);
|
||||
}, [ currentOffer, setPurchaseOptions ]);
|
||||
|
||||
useEffect(() =>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import { FC, useEffect, useRef, useState } from 'react';
|
||||
import { IPurchasableOffer, LocalizeText, Offer, ProductTypeEnum } from '../../../../../api';
|
||||
import { AutoGrid, AutoGridProps, Button, ButtonGroup } from '../../../../../common';
|
||||
import { useCatalog } from '../../../../../hooks';
|
||||
@ -18,6 +18,7 @@ export const CatalogSpacesWidgetView: FC<CatalogSpacesWidgetViewProps> = props =
|
||||
const [ selectedGroupIndex, setSelectedGroupIndex ] = useState(-1);
|
||||
const [ selectedOfferForGroup, setSelectedOfferForGroup ] = useState<IPurchasableOffer[]>(null);
|
||||
const { currentPage = null, currentOffer = null, setCurrentOffer = null, setPurchaseOptions = null } = useCatalog();
|
||||
const elementRef = useRef<HTMLDivElement>();
|
||||
|
||||
const setSelectedOffer = (offer: IPurchasableOffer) =>
|
||||
{
|
||||
@ -91,6 +92,11 @@ export const CatalogSpacesWidgetView: FC<CatalogSpacesWidgetViewProps> = props =
|
||||
});
|
||||
}, [ currentOffer, selectedGroupIndex, selectedOfferForGroup, setPurchaseOptions ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(elementRef && elementRef.current) elementRef.current.scrollTop = 0;
|
||||
}, [ selectedGroupIndex ]);
|
||||
|
||||
if(!groupedOffers || (selectedGroupIndex === -1)) return null;
|
||||
|
||||
const offers = groupedOffers[selectedGroupIndex];
|
||||
@ -100,7 +106,7 @@ export const CatalogSpacesWidgetView: FC<CatalogSpacesWidgetViewProps> = props =
|
||||
<ButtonGroup>
|
||||
{ SPACES_GROUP_NAMES.map((name, index) => <Button key={ index } active={ (selectedGroupIndex === index) } onClick={ event => setSelectedGroupIndex(index) }>{ LocalizeText(`catalog.spaces.tab.${ name }`) }</Button>) }
|
||||
</ButtonGroup>
|
||||
<AutoGrid columnCount={ columnCount } { ...rest }>
|
||||
<AutoGrid innerRef={ elementRef } columnCount={ columnCount } { ...rest }>
|
||||
{ offers && (offers.length > 0) && offers.map((offer, index) => <CatalogGridOfferView key={ index } itemActive={ (currentOffer && (currentOffer === offer)) } offer={ offer } selectOffer={ offer => setSelectedOffer(offer) } />) }
|
||||
{ children }
|
||||
</AutoGrid>
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { GuideSessionGetRequesterRoomMessageComposer, GuideSessionInviteRequesterMessageComposer, GuideSessionMessageMessageComposer, GuideSessionRequesterRoomMessageEvent, GuideSessionResolvedMessageComposer } from '@nitrots/nitro-renderer';
|
||||
import { FC, KeyboardEvent, useCallback, useState } from 'react';
|
||||
import { FC, KeyboardEvent, useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { GetSessionDataManager, GuideToolMessageGroup, LocalizeText, SendMessageComposer, TryVisitRoom } from '../../../api';
|
||||
import { Base, Button, ButtonGroup, Column, Flex, LayoutAvatarImageView, Text } from '../../../common';
|
||||
import { useMessageEvent } from '../../../hooks';
|
||||
@ -16,10 +16,18 @@ interface GuideToolOngoingViewProps
|
||||
|
||||
export const GuideToolOngoingView: FC<GuideToolOngoingViewProps> = props =>
|
||||
{
|
||||
const scrollDiv = useRef<HTMLDivElement>(null);
|
||||
|
||||
const { isGuide = false, userId = 0, userName = null, userFigure = null, isTyping = false, messageGroups = [] } = props;
|
||||
|
||||
const [ messageText, setMessageText ] = useState<string>('');
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
scrollDiv.current?.scrollIntoView({ block: 'end', behavior: 'smooth' });
|
||||
|
||||
}, [ messageGroups ]);
|
||||
|
||||
const visit = useCallback(() =>
|
||||
{
|
||||
SendMessageComposer(new GuideSessionGetRequesterRoomMessageComposer());
|
||||
@ -38,7 +46,7 @@ export const GuideToolOngoingView: FC<GuideToolOngoingViewProps> = props =>
|
||||
useMessageEvent<GuideSessionRequesterRoomMessageEvent>(GuideSessionRequesterRoomMessageEvent, event =>
|
||||
{
|
||||
const parser = event.getParser();
|
||||
|
||||
|
||||
TryVisitRoom(parser.requesterRoomId);
|
||||
});
|
||||
|
||||
@ -100,7 +108,8 @@ export const GuideToolOngoingView: FC<GuideToolOngoingViewProps> = props =>
|
||||
</Base> }
|
||||
</Flex>
|
||||
);
|
||||
}) }
|
||||
}) }
|
||||
<div ref={ scrollDiv } />
|
||||
</Column>
|
||||
</Column>
|
||||
<Column gap={ 1 }>
|
||||
|
@ -70,8 +70,6 @@ export const HelpView: FC<{}> = props =>
|
||||
|
||||
setIsVisible(true);
|
||||
}, [ activeReport ]);
|
||||
|
||||
if(!isVisible && !activeReport) return null;
|
||||
|
||||
const CurrentStepView = () =>
|
||||
{
|
||||
@ -97,19 +95,20 @@ export const HelpView: FC<{}> = props =>
|
||||
|
||||
return (
|
||||
<>
|
||||
<NitroCardView className="nitro-help" theme="primary-slim">
|
||||
<NitroCardHeaderView headerText={ LocalizeText('help.button.cfh') } onCloseClick={ onClose } />
|
||||
<NitroCardContentView className="text-black">
|
||||
<Grid>
|
||||
<Column center size={ 5 } overflow="hidden">
|
||||
<Base className="index-image" />
|
||||
</Column>
|
||||
<Column justifyContent="between" size={ 7 } overflow="hidden">
|
||||
<CurrentStepView />
|
||||
</Column>
|
||||
</Grid>
|
||||
</NitroCardContentView>
|
||||
</NitroCardView>
|
||||
{ isVisible &&
|
||||
<NitroCardView className="nitro-help" theme="primary-slim">
|
||||
<NitroCardHeaderView headerText={ LocalizeText('help.button.cfh') } onCloseClick={ onClose } />
|
||||
<NitroCardContentView className="text-black">
|
||||
<Grid>
|
||||
<Column center size={ 5 } overflow="hidden">
|
||||
<Base className="index-image" />
|
||||
</Column>
|
||||
<Column justifyContent="between" size={ 7 } overflow="hidden">
|
||||
<CurrentStepView />
|
||||
</Column>
|
||||
</Grid>
|
||||
</NitroCardContentView>
|
||||
</NitroCardView> }
|
||||
<SanctionSatusView />
|
||||
<NameChangeView />
|
||||
</>
|
||||
|
@ -21,6 +21,7 @@ export const SelectReportedChatsView: FC<{}> = props =>
|
||||
return messengerHistory.filter(chat => (chat.entityId === activeReport.reportedUserId) && (chat.type === ChatEntryType.TYPE_IM));
|
||||
}
|
||||
|
||||
return [];
|
||||
}, [ activeReport, chatHistory, messengerHistory ]);
|
||||
|
||||
const selectChat = (chatEntry: IChatEntry) =>
|
||||
@ -62,7 +63,7 @@ export const SelectReportedChatsView: FC<{}> = props =>
|
||||
<Text>{ LocalizeText('help.emergency.chat_report.description') }</Text>
|
||||
</Column>
|
||||
<Column gap={ 1 } overflow="hidden">
|
||||
{ !!!userChats.length &&
|
||||
{ !userChats || !userChats.length &&
|
||||
<Text>{ LocalizeText('help.cfh.error.no_user_data') }</Text> }
|
||||
{ (userChats.length > 0) &&
|
||||
<AutoGrid gap={ 1 } columnCount={ 1 } columnMinHeight={ 25 } overflow="auto">
|
||||
|
@ -17,13 +17,11 @@ export const NameChangeView:FC<{}> = props =>
|
||||
const [ layout, setLayout ] = useState<string>(INIT);
|
||||
const [ newUsername, setNewUsername ] = useState<string>('');
|
||||
|
||||
const onHelpNameChangeEvent = useCallback((event: HelpNameChangeEvent) =>
|
||||
useUiEvent<HelpNameChangeEvent>(HelpNameChangeEvent.INIT, event =>
|
||||
{
|
||||
setLayout(INIT);
|
||||
setIsVisible(true);
|
||||
}, []);
|
||||
|
||||
useUiEvent(HelpNameChangeEvent.INIT, onHelpNameChangeEvent);
|
||||
});
|
||||
|
||||
const onAction = useCallback((action: string, value?: string) =>
|
||||
{
|
||||
|
@ -6,14 +6,14 @@ import { useInventoryBadges, useInventoryUnseenTracker } from '../../../../hooks
|
||||
export const InventoryBadgeItemView: FC<PropsWithChildren<{ badgeCode: string }>> = props =>
|
||||
{
|
||||
const { badgeCode = null, children = null, ...rest } = props;
|
||||
const { selectedBadgeCode = null, setSelectedBadgeCode = null, getBadgeId = null } = useInventoryBadges();
|
||||
const { selectedBadgeCode = null, setSelectedBadgeCode = null, toggleBadge = null, getBadgeId = null } = useInventoryBadges();
|
||||
const { isUnseen = null } = useInventoryUnseenTracker();
|
||||
const unseen = isUnseen(UnseenItemCategory.BADGE, getBadgeId(badgeCode));
|
||||
|
||||
return (
|
||||
<LayoutGridItem itemActive={ (selectedBadgeCode === badgeCode) } itemUnseen={ unseen } onMouseDown={ event => setSelectedBadgeCode(badgeCode) } { ...rest }>
|
||||
<LayoutGridItem itemActive={ (selectedBadgeCode === badgeCode) } itemUnseen={ unseen } onMouseDown={ event => setSelectedBadgeCode(badgeCode) } onDoubleClick={ event => toggleBadge(selectedBadgeCode) } { ...rest }>
|
||||
<LayoutBadgeImageView badgeCode={ badgeCode } />
|
||||
{ children }
|
||||
</LayoutGridItem>
|
||||
);
|
||||
}
|
||||
}
|
@ -26,13 +26,16 @@ export const InventoryBotItemView: FC<PropsWithChildren<{ botItem: IBotItem }>>
|
||||
case MouseEventType.ROLL_OUT:
|
||||
if(!isMouseDown || (selectedBot !== botItem)) return;
|
||||
|
||||
attemptBotPlacement(botItem);
|
||||
return;
|
||||
case 'dblclick':
|
||||
attemptBotPlacement(botItem);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<LayoutGridItem itemActive={ (selectedBot === botItem) } itemUnseen={ unseen } onMouseDown={ onMouseEvent } onMouseUp={ onMouseEvent } onMouseOut={ onMouseEvent } { ...rest }>
|
||||
<LayoutGridItem itemActive={ (selectedBot === botItem) } itemUnseen={ unseen } onMouseDown={ onMouseEvent } onMouseUp={ onMouseEvent } onMouseOut={ onMouseEvent } onDoubleClick={ onMouseEvent } { ...rest }>
|
||||
<LayoutAvatarImageView figure={ botItem.botData.figure } direction={ 3 } headOnly={ true } />
|
||||
{ children }
|
||||
</LayoutGridItem>
|
||||
|
@ -24,6 +24,9 @@ export const InventoryFurnitureItemView: FC<{ groupItem: GroupItem }> = props =>
|
||||
case MouseEventType.ROLL_OUT:
|
||||
if(!isMouseDown || !(groupItem === selectedItem)) return;
|
||||
|
||||
attemptItemPlacement(groupItem);
|
||||
return;
|
||||
case 'dblclick':
|
||||
attemptItemPlacement(groupItem);
|
||||
return;
|
||||
}
|
||||
@ -31,5 +34,5 @@ export const InventoryFurnitureItemView: FC<{ groupItem: GroupItem }> = props =>
|
||||
|
||||
const count = groupItem.getUnlockedCount();
|
||||
|
||||
return <LayoutGridItem className={ !count ? 'opacity-0-5 ' : '' } itemImage={ groupItem.iconUrl } itemCount={ groupItem.getUnlockedCount() } itemActive={ (groupItem === selectedItem) } itemUniqueNumber={ groupItem.stuffData.uniqueNumber } itemUnseen={ groupItem.hasUnseenItems } onMouseDown={ onMouseEvent } onMouseUp={ onMouseEvent } onMouseOut={ onMouseEvent } { ...rest } />;
|
||||
return <LayoutGridItem className={ !count ? 'opacity-0-5 ' : '' } itemImage={ groupItem.iconUrl } itemCount={ groupItem.getUnlockedCount() } itemActive={ (groupItem === selectedItem) } itemUniqueNumber={ groupItem.stuffData.uniqueNumber } itemUnseen={ groupItem.hasUnseenItems } onMouseDown={ onMouseEvent } onMouseUp={ onMouseEvent } onMouseOut={ onMouseEvent } onDoubleClick={ onMouseEvent } { ...rest } />;
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ export const InventoryTradeView: FC<InventoryTradeViewProps> = props =>
|
||||
const [ otherGroupItem, setOtherGroupItem ] = useState<GroupItem>(null);
|
||||
const [ filteredGroupItems, setFilteredGroupItems ] = useState<GroupItem[]>(null);
|
||||
const [ countdownTick, setCountdownTick ] = useState(3);
|
||||
const [ quantity, setQuantity ] = useState<number>(1);
|
||||
const { ownUser = null, otherUser = null, groupItems = [], tradeState = TradeState.TRADING_STATE_READY, progressTrade = null, removeItem = null, setTradeState = null } = useInventoryTrade();
|
||||
const { simpleAlert = null } = useNotification();
|
||||
|
||||
@ -118,6 +119,29 @@ export const InventoryTradeView: FC<InventoryTradeViewProps> = props =>
|
||||
return <FontAwesomeIcon icon={ iconName } className={ 'text-' + textColor } />
|
||||
}
|
||||
|
||||
const updateQuantity = (value: number, totalItemCount: number) =>
|
||||
{
|
||||
if(isNaN(Number(value)) || Number(value) < 0 || !value) value = 1;
|
||||
|
||||
value = Math.max(Number(value), 1);
|
||||
value = Math.min(Number(value), totalItemCount);
|
||||
|
||||
if(value === quantity) return;
|
||||
|
||||
setQuantity(value);
|
||||
}
|
||||
|
||||
const changeCount = (totalItemCount: number) =>
|
||||
{
|
||||
updateQuantity(quantity, totalItemCount);
|
||||
attemptItemOffer(quantity);
|
||||
}
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
setQuantity(1);
|
||||
}, [ groupItem ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(tradeState !== TradeState.TRADING_STATE_COUNTDOWN) return;
|
||||
@ -159,18 +183,29 @@ export const InventoryTradeView: FC<InventoryTradeViewProps> = props =>
|
||||
const count = item.getUnlockedCount();
|
||||
|
||||
return (
|
||||
<LayoutGridItem key={ index } className={ !count ? 'opacity-0-5 ' : '' } itemImage={ item.iconUrl } itemCount={ count } itemActive={ (groupItem === item) } itemUniqueNumber={ item.stuffData.uniqueNumber } onClick={ event => (count && setGroupItem(item)) }>
|
||||
<LayoutGridItem key={ index } className={ !count ? 'opacity-0-5 ' : '' } itemImage={ item.iconUrl } itemCount={ count } itemActive={ (groupItem === item) } itemUniqueNumber={ item.stuffData.uniqueNumber } onClick={ event => (count && setGroupItem(item)) } onDoubleClick={ event => attemptItemOffer(1) }>
|
||||
{ ((count > 0) && (groupItem === item)) &&
|
||||
<Button position="absolute" variant="success" className="trade-button bottom-1 end-1" onClick={ event => attemptItemOffer(1) }>
|
||||
<FontAwesomeIcon icon="chevron-right" />
|
||||
</Button> }
|
||||
<Button position="absolute" variant="success" className="trade-button bottom-1 end-1" onClick={ event => attemptItemOffer(1) }>
|
||||
<FontAwesomeIcon icon="chevron-right" />
|
||||
</Button>
|
||||
}
|
||||
</LayoutGridItem>
|
||||
);
|
||||
}) }
|
||||
</AutoGrid>
|
||||
<Base fullWidth className="badge bg-muted">
|
||||
{ groupItem ? groupItem.name : LocalizeText('catalog_selectproduct') }
|
||||
</Base>
|
||||
<Column gap={ 1 } alignItems="end">
|
||||
<Grid overflow="hidden">
|
||||
<Column size={ 6 } overflow="hidden">
|
||||
<input type="number" className="form-control form-control-sm quantity-input" placeholder={ LocalizeText('catalog.bundlewidget.spinner.select.amount') } disabled={ !groupItem } value={ quantity } onChange={ event => setQuantity(event.target.valueAsNumber) } />
|
||||
</Column>
|
||||
<Column size={ 6 } overflow="hidden">
|
||||
<Button variant="secondary" disabled={ !groupItem } onClick={ event => changeCount(groupItem.getUnlockedCount()) }>{ LocalizeText('inventory.trading.areoffering') }</Button>
|
||||
</Column>
|
||||
</Grid>
|
||||
<Base fullWidth className="badge bg-muted">
|
||||
{ groupItem ? groupItem.name : LocalizeText('catalog_selectproduct') }
|
||||
</Base>
|
||||
</Column>
|
||||
</Flex>
|
||||
</Column>
|
||||
<Column size={ 8 } overflow="hidden">
|
||||
@ -188,11 +223,11 @@ export const InventoryTradeView: FC<InventoryTradeViewProps> = props =>
|
||||
if(!item) return <LayoutGridItem key={ i } />;
|
||||
|
||||
return (
|
||||
<LayoutGridItem key={ i } itemActive={ (ownGroupItem === item) } itemImage={ item.iconUrl } itemCount={ item.getTotalCount() } itemUniqueNumber={ item.stuffData.uniqueNumber } onClick={ event => setOwnGroupItem(item) }>
|
||||
<LayoutGridItem key={ i } itemActive={ (ownGroupItem === item) } itemImage={ item.iconUrl } itemCount={ item.getTotalCount() } itemUniqueNumber={ item.stuffData.uniqueNumber } onClick={ event => setOwnGroupItem(item) } onDoubleClick={ event => removeItem(item) }>
|
||||
{ (ownGroupItem === item) &&
|
||||
<Button position="absolute" variant="danger" className="trade-button bottom-1 start-1" onClick={ event => removeItem(item) }>
|
||||
<FontAwesomeIcon icon="chevron-left" />
|
||||
</Button> }
|
||||
<Button position="absolute" variant="danger" className="trade-button bottom-1 start-1" onClick={ event => removeItem(item) }>
|
||||
<FontAwesomeIcon icon="chevron-left" />
|
||||
</Button> }
|
||||
</LayoutGridItem>
|
||||
);
|
||||
}) }
|
||||
|
@ -26,13 +26,16 @@ export const InventoryPetItemView: FC<PropsWithChildren<{ petItem: IPetItem }>>
|
||||
case MouseEventType.ROLL_OUT:
|
||||
if(!isMouseDown || !(petItem === selectedPet)) return;
|
||||
|
||||
attemptPetPlacement(petItem);
|
||||
return;
|
||||
case 'dblclick':
|
||||
attemptPetPlacement(petItem);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<LayoutGridItem itemActive={ (petItem === selectedPet) } itemUnseen={ unseen } onMouseDown={ onMouseEvent } onMouseUp={ onMouseEvent } onMouseOut={ onMouseEvent } { ...rest }>
|
||||
<LayoutGridItem itemActive={ (petItem === selectedPet) } itemUnseen={ unseen } onMouseDown={ onMouseEvent } onMouseUp={ onMouseEvent } onMouseOut={ onMouseEvent } onDoubleClick={ onMouseEvent } { ...rest }>
|
||||
<LayoutPetImageView figure={ petItem.petData.figureData.figuredata } direction={ 3 } headOnly={ true } />
|
||||
{ children }
|
||||
</LayoutGridItem>
|
||||
|
@ -24,6 +24,7 @@ export const NavigatorView: FC<{}> = props =>
|
||||
const [ needsSearch, setNeedsSearch ] = useState(false);
|
||||
const { searchResult = null, topLevelContext = null, topLevelContexts = null, navigatorData = null } = useNavigator();
|
||||
const pendingSearch = useRef<{ value: string, code: string }>(null);
|
||||
const elementRef = useRef<HTMLDivElement>();
|
||||
|
||||
useRoomSessionManagerEvent<RoomSessionEvent>(RoomSessionEvent.CREATED, event =>
|
||||
{
|
||||
@ -158,6 +159,8 @@ export const NavigatorView: FC<{}> = props =>
|
||||
if(!searchResult) return;
|
||||
|
||||
setIsLoading(false);
|
||||
|
||||
if(elementRef && elementRef.current) elementRef.current.scrollTop = 0;
|
||||
}, [ searchResult ]);
|
||||
|
||||
useEffect(() =>
|
||||
@ -214,7 +217,7 @@ export const NavigatorView: FC<{}> = props =>
|
||||
{ !isCreatorOpen &&
|
||||
<>
|
||||
<NavigatorSearchView sendSearch={ sendSearch } />
|
||||
<Column overflow="auto">
|
||||
<Column innerRef={ elementRef } overflow="auto">
|
||||
{ (searchResult && searchResult.results.map((result, index) => <NavigatorSearchResultView key={ index } searchResult={ result } />)) }
|
||||
</Column>
|
||||
</> }
|
||||
|
@ -13,7 +13,6 @@ export interface NavigatorSearchResultViewProps extends AutoGridProps
|
||||
export const NavigatorSearchResultView: FC<NavigatorSearchResultViewProps> = props =>
|
||||
{
|
||||
const { searchResult = null, ...rest } = props;
|
||||
|
||||
const [ isExtended, setIsExtended ] = useState(true);
|
||||
const [ displayMode, setDisplayMode ] = useState<number>(0);
|
||||
|
||||
@ -44,7 +43,7 @@ export const NavigatorSearchResultView: FC<NavigatorSearchResultViewProps> = pro
|
||||
|
||||
//setIsExtended(searchResult.closed);
|
||||
setDisplayMode(searchResult.mode);
|
||||
}, [ searchResult,props ]);
|
||||
}, [ searchResult ]);
|
||||
|
||||
const gridHasTwoColumns = (displayMode >= NavigatorSearchResultViewDisplayMode.THUMBNAILS);
|
||||
|
||||
|
@ -70,6 +70,17 @@
|
||||
}
|
||||
}
|
||||
|
||||
.body-image-plant {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
max-width: 68px;
|
||||
height: 85px;
|
||||
max-height: 90px;
|
||||
border-radius: $border-radius;
|
||||
}
|
||||
|
||||
.badge-image {
|
||||
width: 45px;
|
||||
height: 45px;
|
||||
|
@ -1,7 +1,9 @@
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { FC } from 'react';
|
||||
import { AvatarInfoPet, LocalizeText } from '../../../../../api';
|
||||
import { Base, Column, Flex, LayoutPetImageView, Text, UserProfileIconView } from '../../../../../common';
|
||||
import { PetRespectComposer, PetType } from '@nitrots/nitro-renderer';
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import { AvatarInfoPet, ConvertSeconds, CreateLinkEvent, GetConfiguration, LocalizeText, SendMessageComposer } from '../../../../../api';
|
||||
import { Base, Button, Column, Flex, LayoutCounterTimeView, LayoutPetImageView, LayoutRarityLevelView, Text, UserProfileIconView } from '../../../../../common';
|
||||
import { useRoom, useSessionInfo } from '../../../../../hooks';
|
||||
|
||||
interface InfoStandWidgetPetViewProps
|
||||
{
|
||||
@ -12,72 +14,196 @@ interface InfoStandWidgetPetViewProps
|
||||
export const InfoStandWidgetPetView: FC<InfoStandWidgetPetViewProps> = props =>
|
||||
{
|
||||
const { avatarInfo = null, onClose = null } = props;
|
||||
const [ remainingGrowTime, setRemainingGrowTime ] = useState(0);
|
||||
const [ remainingTimeToLive, setRemainingTimeToLive ] = useState(0);
|
||||
const { roomSession = null } = useRoom();
|
||||
const { petRespectRemaining = 0, respectPet = null } = useSessionInfo();
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
setRemainingGrowTime(avatarInfo.remainingGrowTime);
|
||||
setRemainingTimeToLive(avatarInfo.remainingTimeToLive);
|
||||
}, [ avatarInfo ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if((avatarInfo.petType !== PetType.MONSTERPLANT) || avatarInfo.dead) return;
|
||||
|
||||
const interval = setInterval(() =>
|
||||
{
|
||||
setRemainingGrowTime(prevValue => (prevValue - 1));
|
||||
setRemainingTimeToLive(prevValue => (prevValue - 1));
|
||||
}, 1000);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, [ avatarInfo ]);
|
||||
|
||||
if(!avatarInfo) return null;
|
||||
|
||||
const processButtonAction = (action: string) =>
|
||||
{
|
||||
let hideMenu = true;
|
||||
|
||||
if (!action || action == '') return;
|
||||
|
||||
switch (action)
|
||||
{
|
||||
case 'respect':
|
||||
respectPet(avatarInfo.id);
|
||||
|
||||
if((petRespectRemaining - 1) >= 1) hideMenu = false;
|
||||
break;
|
||||
case 'buyfood':
|
||||
CreateLinkEvent('catalog/open/' + GetConfiguration('catalog.links')['pets.buy_saddle']);
|
||||
break;
|
||||
case 'train':
|
||||
// not coded
|
||||
break;
|
||||
case 'treat':
|
||||
SendMessageComposer(new PetRespectComposer(avatarInfo.id));
|
||||
break;
|
||||
case 'compost':
|
||||
roomSession?.compostPlant(avatarInfo.id);
|
||||
break;
|
||||
case 'pick_up':
|
||||
roomSession?.pickupPet(avatarInfo.id);
|
||||
break;
|
||||
}
|
||||
|
||||
if(hideMenu) onClose();
|
||||
}
|
||||
|
||||
return (
|
||||
<Column className="nitro-infostand rounded">
|
||||
<Column overflow="visible" className="container-fluid content-area" gap={ 1 }>
|
||||
<Column gap={ 1 }>
|
||||
<Flex alignItems="center" justifyContent="between" gap={ 1 }>
|
||||
<Text variant="white" small wrap>{ avatarInfo.name }</Text>
|
||||
<FontAwesomeIcon icon="times" className="cursor-pointer" onClick={ onClose } />
|
||||
</Flex>
|
||||
<Text variant="white" small wrap>{ LocalizeText(`pet.breed.${ avatarInfo.petType }.${ avatarInfo.petBreed }`) }</Text>
|
||||
<hr className="m-0" />
|
||||
</Column>
|
||||
<Column gap={ 1 }>
|
||||
<Flex gap={ 1 }>
|
||||
<Column fullWidth overflow="hidden" className="body-image pet p-1">
|
||||
<LayoutPetImageView figure={ avatarInfo.petFigure } posture={ avatarInfo.posture } direction={ 4 } />
|
||||
</Column>
|
||||
<Column grow gap={ 1 }>
|
||||
<Text variant="white" center small wrap>{ LocalizeText('pet.level', [ 'level', 'maxlevel' ], [ avatarInfo.level.toString(), avatarInfo.maximumLevel.toString() ]) }</Text>
|
||||
<Column alignItems="center" gap={ 1 }>
|
||||
<Text variant="white" small truncate>{ LocalizeText('infostand.pet.text.happiness') }</Text>
|
||||
<Base fullWidth overflow="hidden" position="relative" className="bg-light-dark rounded">
|
||||
<Flex fit center position="absolute">
|
||||
<Text variant="white" small>{ avatarInfo.happyness + '/' + avatarInfo.maximumHappyness }</Text>
|
||||
</Flex>
|
||||
<Base className="bg-info rounded pet-stats" style={ { width: (avatarInfo.happyness / avatarInfo.maximumHappyness) * 100 + '%' } } />
|
||||
</Base>
|
||||
<Column gap={ 1 } alignItems="end">
|
||||
<Column className="nitro-infostand rounded">
|
||||
<Column overflow="visible" className="container-fluid content-area" gap={ 1 }>
|
||||
<Column gap={ 1 }>
|
||||
<Flex alignItems="center" justifyContent="between" gap={ 1 }>
|
||||
<Text variant="white" small wrap>{ avatarInfo.name }</Text>
|
||||
<FontAwesomeIcon icon="times" className="cursor-pointer" onClick={ onClose } />
|
||||
</Flex>
|
||||
<Text variant="white" small wrap>{ LocalizeText(`pet.breed.${ avatarInfo.petType }.${ avatarInfo.petBreed }`) }</Text>
|
||||
<hr className="m-0" />
|
||||
</Column>
|
||||
{ (avatarInfo.petType === PetType.MONSTERPLANT) &&
|
||||
<>
|
||||
<Column center gap={ 1 }>
|
||||
<LayoutPetImageView figure={ avatarInfo.petFigure } posture={ avatarInfo.posture } direction={ 4 } />
|
||||
<hr className="m-0" />
|
||||
</Column>
|
||||
<Column alignItems="center" gap={ 1 }>
|
||||
<Text variant="white" small truncate>{ LocalizeText('infostand.pet.text.experience') }</Text>
|
||||
<Base fullWidth overflow="hidden" position="relative" className="bg-light-dark rounded">
|
||||
<Flex fit center position="absolute">
|
||||
<Text variant="white" small>{ avatarInfo.experience + '/' + avatarInfo.levelExperienceGoal }</Text>
|
||||
</Flex>
|
||||
<Base className="bg-purple rounded pet-stats" style={ { width: (avatarInfo.experience / avatarInfo.levelExperienceGoal) * 100 + '%' } } />
|
||||
</Base>
|
||||
<Column gap={ 2 }>
|
||||
{ !avatarInfo.dead &&
|
||||
<Column alignItems="center" gap={ 1 }>
|
||||
<Text variant="white" center small wrap>{ LocalizeText('pet.level', [ 'level', 'maxlevel' ], [ avatarInfo.level.toString(), avatarInfo.maximumLevel.toString() ]) }</Text>
|
||||
</Column> }
|
||||
<Column alignItems="center" gap={ 1 }>
|
||||
<Text variant="white" small truncate>{ LocalizeText('infostand.pet.text.wellbeing') }</Text>
|
||||
<Base fullWidth overflow="hidden" position="relative" className="bg-light-dark rounded">
|
||||
<Flex fit center position="absolute">
|
||||
<Text variant="white" small>{ avatarInfo.dead ? '00:00:00' : ConvertSeconds((remainingTimeToLive == 0 ? avatarInfo.remainingTimeToLive : remainingTimeToLive)).split(':')[1] + ':' + ConvertSeconds((remainingTimeToLive == null || remainingTimeToLive == undefined ? 0 : remainingTimeToLive)).split(':')[2] + ':' + ConvertSeconds((remainingTimeToLive == null || remainingTimeToLive == undefined ? 0 : remainingTimeToLive)).split(':')[3] }</Text>
|
||||
</Flex>
|
||||
<Base className="bg-success rounded pet-stats" style={ { width: avatarInfo.dead ? '0' : Math.round((avatarInfo.maximumTimeToLive * 100) / (remainingTimeToLive)).toString() } } />
|
||||
</Base>
|
||||
</Column>
|
||||
{ remainingGrowTime != 0 && remainingGrowTime > 0 &&
|
||||
<Column alignItems="center" gap={ 1 }>
|
||||
<Text variant="white" small truncate>{ LocalizeText('infostand.pet.text.growth') }</Text>
|
||||
<LayoutCounterTimeView className="top-2 end-2" day={ ConvertSeconds(remainingGrowTime).split(':')[0] } hour={ ConvertSeconds(remainingGrowTime).split(':')[1] } minutes={ ConvertSeconds(remainingGrowTime).split(':')[2] } seconds={ ConvertSeconds(remainingGrowTime).split(':')[3] } />
|
||||
</Column> }
|
||||
<Column alignItems="center" gap={ 1 }>
|
||||
<Text variant="white" small truncate>{ LocalizeText('infostand.pet.text.raritylevel', [ 'level' ], [ LocalizeText(`infostand.pet.raritylevel.${ avatarInfo.rarityLevel }`) ]) }</Text>
|
||||
<LayoutRarityLevelView className="top-2 end-2" level={ avatarInfo.rarityLevel } />
|
||||
</Column>
|
||||
<hr className="m-0" />
|
||||
</Column>
|
||||
<Column alignItems="center" gap={ 1 }>
|
||||
<Text variant="white" small truncate>{ LocalizeText('infostand.pet.text.energy') }</Text>
|
||||
<Base fullWidth overflow="hidden" position="relative" className="bg-light-dark rounded">
|
||||
<Flex fit center position="absolute">
|
||||
<Text variant="white" small>{ avatarInfo.energy + '/' + avatarInfo.maximumEnergy }</Text>
|
||||
</Flex>
|
||||
<Base className="bg-success rounded pet-stats" style={ { width: (avatarInfo.energy / avatarInfo.maximumEnergy) * 100 + '%' } } />
|
||||
</Base>
|
||||
<Column gap={ 1 }>
|
||||
<Text variant="white" small wrap>{ LocalizeText('pet.age', [ 'age' ], [ avatarInfo.age.toString() ]) }</Text>
|
||||
<hr className="m-0" />
|
||||
</Column>
|
||||
</Column>
|
||||
</Flex>
|
||||
<hr className="m-0" />
|
||||
</Column>
|
||||
<Column gap={ 1 }>
|
||||
<Text variant="white" small wrap>{ LocalizeText('infostand.text.petrespect', [ 'count' ], [ avatarInfo.respect.toString() ]) }</Text>
|
||||
<Text variant="white" small wrap>{ LocalizeText('pet.age', [ 'age' ], [ avatarInfo.age.toString() ]) }</Text>
|
||||
<hr className="m-0" />
|
||||
</Column>
|
||||
<Column gap={ 1 }>
|
||||
<Flex alignItems="center" gap={ 1 }>
|
||||
<UserProfileIconView userId={ avatarInfo.ownerId } />
|
||||
<Text variant="white" small wrap>
|
||||
{ LocalizeText('infostand.text.petowner', [ 'name' ], [ avatarInfo.ownerName ]) }
|
||||
</Text>
|
||||
</Flex>
|
||||
</> }
|
||||
{ (avatarInfo.petType !== PetType.MONSTERPLANT) &&
|
||||
<>
|
||||
<Column gap={ 1 }>
|
||||
<Flex gap={ 1 }>
|
||||
<Column fullWidth overflow="hidden" className="body-image pet p-1">
|
||||
<LayoutPetImageView figure={ avatarInfo.petFigure } posture={ avatarInfo.posture } direction={ 4 } />
|
||||
</Column>
|
||||
<Column grow gap={ 1 }>
|
||||
<Text variant="white" center small wrap>{ LocalizeText('pet.level', [ 'level', 'maxlevel' ], [ avatarInfo.level.toString(), avatarInfo.maximumLevel.toString() ]) }</Text>
|
||||
<Column alignItems="center" gap={ 1 }>
|
||||
<Text variant="white" small truncate>{ LocalizeText('infostand.pet.text.happiness') }</Text>
|
||||
<Base fullWidth overflow="hidden" position="relative" className="bg-light-dark rounded">
|
||||
<Flex fit center position="absolute">
|
||||
<Text variant="white" small>{ avatarInfo.happyness + '/' + avatarInfo.maximumHappyness }</Text>
|
||||
</Flex>
|
||||
<Base className="bg-info rounded pet-stats" style={ { width: (avatarInfo.happyness / avatarInfo.maximumHappyness) * 100 + '%' } } />
|
||||
</Base>
|
||||
</Column>
|
||||
<Column alignItems="center" gap={ 1 }>
|
||||
<Text variant="white" small truncate>{ LocalizeText('infostand.pet.text.experience') }</Text>
|
||||
<Base fullWidth overflow="hidden" position="relative" className="bg-light-dark rounded">
|
||||
<Flex fit center position="absolute">
|
||||
<Text variant="white" small>{ avatarInfo.experience + '/' + avatarInfo.levelExperienceGoal }</Text>
|
||||
</Flex>
|
||||
<Base className="bg-purple rounded pet-stats" style={ { width: (avatarInfo.experience / avatarInfo.levelExperienceGoal) * 100 + '%' } } />
|
||||
</Base>
|
||||
</Column>
|
||||
<Column alignItems="center" gap={ 1 }>
|
||||
<Text variant="white" small truncate>{ LocalizeText('infostand.pet.text.energy') }</Text>
|
||||
<Base fullWidth overflow="hidden" position="relative" className="bg-light-dark rounded">
|
||||
<Flex fit center position="absolute">
|
||||
<Text variant="white" small>{ avatarInfo.energy + '/' + avatarInfo.maximumEnergy }</Text>
|
||||
</Flex>
|
||||
<Base className="bg-success rounded pet-stats" style={ { width: (avatarInfo.energy / avatarInfo.maximumEnergy) * 100 + '%' } } />
|
||||
</Base>
|
||||
</Column>
|
||||
</Column>
|
||||
</Flex>
|
||||
<hr className="m-0" />
|
||||
</Column>
|
||||
<Column gap={ 1 }>
|
||||
{ (avatarInfo.petType !== PetType.MONSTERPLANT) &&
|
||||
<Text variant="white" small wrap>{ LocalizeText('infostand.text.petrespect', [ 'count' ], [ avatarInfo.respect.toString() ]) }</Text> }
|
||||
<Text variant="white" small wrap>{ LocalizeText('pet.age', [ 'age' ], [ avatarInfo.age.toString() ]) }</Text>
|
||||
<hr className="m-0" />
|
||||
</Column>
|
||||
</> }
|
||||
<Column gap={ 1 }>
|
||||
<Flex alignItems="center" gap={ 1 }>
|
||||
<UserProfileIconView userId={ avatarInfo.ownerId } />
|
||||
<Text variant="white" small wrap>
|
||||
{ LocalizeText('infostand.text.petowner', [ 'name' ], [ avatarInfo.ownerName ]) }
|
||||
</Text>
|
||||
</Flex>
|
||||
</Column>
|
||||
</Column>
|
||||
</Column>
|
||||
<Flex gap={ 1 } justifyContent="end">
|
||||
{ (avatarInfo.petType !== PetType.MONSTERPLANT) &&
|
||||
<Button variant="dark" onClick={ event => processButtonAction('buyfood') }>
|
||||
{ LocalizeText('infostand.button.buyfood') }
|
||||
</Button> }
|
||||
{ avatarInfo.isOwner && (avatarInfo.petType !== PetType.MONSTERPLANT) &&
|
||||
<Button variant="dark" onClick={ event => processButtonAction('train') }>
|
||||
{ LocalizeText('infostand.button.train') }
|
||||
</Button> }
|
||||
{ !avatarInfo.dead && ((avatarInfo.energy / avatarInfo.maximumEnergy) < 0.98) && (avatarInfo.petType === PetType.MONSTERPLANT) &&
|
||||
<Button variant="dark" onClick={ event => processButtonAction('treat') }>
|
||||
{ LocalizeText('infostand.button.pettreat') }
|
||||
</Button> }
|
||||
{ roomSession?.isRoomOwner && (avatarInfo.petType === PetType.MONSTERPLANT) &&
|
||||
<Button variant="dark" onClick={ event => processButtonAction('compost') }>
|
||||
{ LocalizeText('infostand.button.compost') }
|
||||
</Button> }
|
||||
{ avatarInfo.isOwner &&
|
||||
<Button variant="dark" onClick={ event => processButtonAction('pick_up') }>
|
||||
{ LocalizeText('inventory.pets.pickup') }
|
||||
</Button> }
|
||||
{ (petRespectRemaining > 0) && (avatarInfo.petType !== PetType.MONSTERPLANT) &&
|
||||
<Button variant="dark" onClick={ event => processButtonAction('respect') }>
|
||||
{ LocalizeText('infostand.button.petrespect', [ 'count' ], [ petRespectRemaining.toString() ]) }
|
||||
</Button> }
|
||||
</Flex>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ import { RoomControllerLevel, RoomObjectCategory, RoomObjectVariable, RoomUnitGi
|
||||
import { FC, useEffect, useMemo, useState } from 'react';
|
||||
import { AvatarInfoUser, CreateLinkEvent, DispatchUiEvent, GetOwnRoomObject, GetSessionDataManager, GetUserProfile, LocalizeText, MessengerFriend, ReportType, RoomWidgetUpdateChatInputContentEvent, SendMessageComposer } from '../../../../../api';
|
||||
import { Base, Flex } from '../../../../../common';
|
||||
import { useFriends, useHelp, useRoom } from '../../../../../hooks';
|
||||
import { useFriends, useHelp, useRoom, useSessionInfo } from '../../../../../hooks';
|
||||
import { ContextMenuHeaderView } from '../../context-menu/ContextMenuHeaderView';
|
||||
import { ContextMenuListItemView } from '../../context-menu/ContextMenuListItemView';
|
||||
import { ContextMenuView } from '../../context-menu/ContextMenuView';
|
||||
@ -26,10 +26,10 @@ export const AvatarInfoWidgetAvatarView: FC<AvatarInfoWidgetAvatarViewProps> = p
|
||||
{
|
||||
const { avatarInfo = null, onClose = null } = props;
|
||||
const [ mode, setMode ] = useState(MODE_NORMAL);
|
||||
const [ respectsLeft, setRespectsLeft ] = useState(0);
|
||||
const { canRequestFriend = null } = useFriends();
|
||||
const { report = null } = useHelp();
|
||||
const { roomSession = null } = useRoom();
|
||||
const { userRespectRemaining = 0, respectUser = null } = useSessionInfo();
|
||||
|
||||
const isShowGiveRights = useMemo(() =>
|
||||
{
|
||||
@ -113,13 +113,9 @@ export const AvatarInfoWidgetAvatarView: FC<AvatarInfoWidgetAvatarViewProps> = p
|
||||
setMode(MODE_RELATIONSHIP);
|
||||
break;
|
||||
case 'respect': {
|
||||
let newRespectsLeft = (respectsLeft - 1);
|
||||
|
||||
setRespectsLeft(newRespectsLeft);
|
||||
respectUser(avatarInfo.webID);
|
||||
|
||||
GetSessionDataManager().giveRespect(avatarInfo.webID);
|
||||
|
||||
if(newRespectsLeft > 0) hideMenu = false;
|
||||
if((userRespectRemaining - 1) >= 1) hideMenu = false;
|
||||
break;
|
||||
}
|
||||
case 'ignore':
|
||||
@ -203,7 +199,6 @@ export const AvatarInfoWidgetAvatarView: FC<AvatarInfoWidgetAvatarViewProps> = p
|
||||
useEffect(() =>
|
||||
{
|
||||
setMode(MODE_NORMAL);
|
||||
setRespectsLeft(avatarInfo.respectLeft);
|
||||
}, [ avatarInfo ]);
|
||||
|
||||
return (
|
||||
@ -223,9 +218,9 @@ export const AvatarInfoWidgetAvatarView: FC<AvatarInfoWidgetAvatarViewProps> = p
|
||||
<ContextMenuListItemView onClick={ event => processAction('whisper') }>
|
||||
{ LocalizeText('infostand.button.whisper') }
|
||||
</ContextMenuListItemView>
|
||||
{ (respectsLeft > 0) &&
|
||||
{ (userRespectRemaining > 0) &&
|
||||
<ContextMenuListItemView onClick={ event => processAction('respect') }>
|
||||
{ LocalizeText('infostand.button.respect', [ 'count' ], [ respectsLeft.toString() ]) }
|
||||
{ LocalizeText('infostand.button.respect', [ 'count' ], [ userRespectRemaining.toString() ]) }
|
||||
</ContextMenuListItemView> }
|
||||
{ !canRequestFriend(avatarInfo.webID) &&
|
||||
<ContextMenuListItemView onClick={ event => processAction('relationship') }>
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { PetRespectComposer, PetType, RoomObjectCategory, RoomObjectType, RoomObjectVariable, RoomUnitGiveHandItemPetComposer } from '@nitrots/nitro-renderer';
|
||||
import { FC, useEffect, useMemo, useState } from 'react';
|
||||
import { AvatarInfoPet, CreateLinkEvent, GetConfiguration, GetOwnRoomObject, GetSessionDataManager, LocalizeText, SendMessageComposer } from '../../../../../api';
|
||||
import { useRoom } from '../../../../../hooks';
|
||||
import { AvatarInfoPet, CreateLinkEvent, GetConfiguration, GetOwnRoomObject, LocalizeText, SendMessageComposer } from '../../../../../api';
|
||||
import { useRoom, useSessionInfo } from '../../../../../hooks';
|
||||
import { ContextMenuHeaderView } from '../../context-menu/ContextMenuHeaderView';
|
||||
import { ContextMenuListItemView } from '../../context-menu/ContextMenuListItemView';
|
||||
import { ContextMenuView } from '../../context-menu/ContextMenuView';
|
||||
@ -21,8 +21,8 @@ export const AvatarInfoWidgetOwnPetView: FC<AvatarInfoWidgetOwnPetViewProps> = p
|
||||
{
|
||||
const { avatarInfo = null, onClose = null } = props;
|
||||
const [ mode, setMode ] = useState(MODE_NORMAL);
|
||||
const [ respectsLeft, setRespectsLeft ] = useState(0);
|
||||
const { roomSession = null } = useRoom();
|
||||
const { petRespectRemaining = 0, respectPet = null } = useSessionInfo();
|
||||
|
||||
const canGiveHandItem = useMemo(() =>
|
||||
{
|
||||
@ -49,18 +49,9 @@ export const AvatarInfoWidgetOwnPetView: FC<AvatarInfoWidgetOwnPetViewProps> = p
|
||||
switch(name)
|
||||
{
|
||||
case 'respect':
|
||||
let newRespectsLeft = 0;
|
||||
respectPet(avatarInfo.id);
|
||||
|
||||
setRespectsLeft(prevValue =>
|
||||
{
|
||||
newRespectsLeft = (prevValue - 1);
|
||||
|
||||
return newRespectsLeft;
|
||||
});
|
||||
|
||||
GetSessionDataManager().givePetRespect(avatarInfo.id);
|
||||
|
||||
if(newRespectsLeft > 0) hideMenu = false;
|
||||
if((petRespectRemaining - 1) >= 1) hideMenu = false;
|
||||
break;
|
||||
case 'treat':
|
||||
SendMessageComposer(new PetRespectComposer(avatarInfo.id));
|
||||
@ -131,8 +122,6 @@ export const AvatarInfoWidgetOwnPetView: FC<AvatarInfoWidgetOwnPetViewProps> = p
|
||||
|
||||
return MODE_NORMAL;
|
||||
});
|
||||
|
||||
setRespectsLeft(avatarInfo.respectsPetLeft);
|
||||
}, [ avatarInfo ]);
|
||||
|
||||
return (
|
||||
@ -142,9 +131,9 @@ export const AvatarInfoWidgetOwnPetView: FC<AvatarInfoWidgetOwnPetViewProps> = p
|
||||
</ContextMenuHeaderView>
|
||||
{ (mode === MODE_NORMAL) &&
|
||||
<>
|
||||
{ (respectsLeft > 0) &&
|
||||
{ (petRespectRemaining > 0) &&
|
||||
<ContextMenuListItemView onClick={ event => processAction('respect') }>
|
||||
{ LocalizeText('infostand.button.petrespect', [ 'count' ], [ respectsLeft.toString() ]) }
|
||||
{ LocalizeText('infostand.button.petrespect', [ 'count' ], [ petRespectRemaining.toString() ]) }
|
||||
</ContextMenuListItemView> }
|
||||
<ContextMenuListItemView onClick={ event => processAction('train') }>
|
||||
{ LocalizeText('infostand.button.train') }
|
||||
@ -170,9 +159,9 @@ export const AvatarInfoWidgetOwnPetView: FC<AvatarInfoWidgetOwnPetViewProps> = p
|
||||
<input type="checkbox" checked={ !!avatarInfo.publiclyRideable } readOnly={ true } />
|
||||
{ LocalizeText('infostand.button.toggle_riding_permission') }
|
||||
</ContextMenuListItemView>
|
||||
{ (respectsLeft > 0) &&
|
||||
{ (petRespectRemaining > 0) &&
|
||||
<ContextMenuListItemView onClick={ event => processAction('respect') }>
|
||||
{ LocalizeText('infostand.button.petrespect', [ 'count' ], [ respectsLeft.toString() ]) }
|
||||
{ LocalizeText('infostand.button.petrespect', [ 'count' ], [ petRespectRemaining.toString() ]) }
|
||||
</ContextMenuListItemView> }
|
||||
<ContextMenuListItemView onClick={ event => processAction('train') }>
|
||||
{ LocalizeText('infostand.button.train') }
|
||||
@ -189,9 +178,9 @@ export const AvatarInfoWidgetOwnPetView: FC<AvatarInfoWidgetOwnPetViewProps> = p
|
||||
<ContextMenuListItemView onClick={ event => processAction('dismount') }>
|
||||
{ LocalizeText('infostand.button.dismount') }
|
||||
</ContextMenuListItemView>
|
||||
{ (respectsLeft > 0) &&
|
||||
{ (petRespectRemaining > 0) &&
|
||||
<ContextMenuListItemView onClick={ event => processAction('respect') }>
|
||||
{ LocalizeText('infostand.button.petrespect', [ 'count' ], [ respectsLeft.toString() ]) }
|
||||
{ LocalizeText('infostand.button.petrespect', [ 'count' ], [ petRespectRemaining.toString() ]) }
|
||||
</ContextMenuListItemView> }
|
||||
</> }
|
||||
{ (mode === MODE_MONSTER_PLANT) &&
|
||||
@ -209,7 +198,7 @@ export const AvatarInfoWidgetOwnPetView: FC<AvatarInfoWidgetOwnPetViewProps> = p
|
||||
</ContextMenuListItemView> }
|
||||
{ !avatarInfo.dead && ((avatarInfo.energy / avatarInfo.maximumEnergy) < 0.98) &&
|
||||
<ContextMenuListItemView onClick={ event => processAction('treat') }>
|
||||
{ LocalizeText('infostand.button.treat') }
|
||||
{ LocalizeText('infostand.button.pettreat') }
|
||||
</ContextMenuListItemView> }
|
||||
{ !avatarInfo.dead && (avatarInfo.level === avatarInfo.maximumLevel) && avatarInfo.breedable &&
|
||||
<>
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { PetRespectComposer, PetType, RoomControllerLevel, RoomObjectCategory, RoomObjectType, RoomObjectVariable, RoomUnitGiveHandItemPetComposer } from '@nitrots/nitro-renderer';
|
||||
import { FC, useEffect, useMemo, useState } from 'react';
|
||||
import { AvatarInfoPet, GetOwnRoomObject, GetSessionDataManager, LocalizeText, SendMessageComposer } from '../../../../../api';
|
||||
import { useRoom } from '../../../../../hooks';
|
||||
import { useRoom, useSessionInfo } from '../../../../../hooks';
|
||||
import { ContextMenuHeaderView } from '../../context-menu/ContextMenuHeaderView';
|
||||
import { ContextMenuListItemView } from '../../context-menu/ContextMenuListItemView';
|
||||
import { ContextMenuView } from '../../context-menu/ContextMenuView';
|
||||
@ -21,8 +21,8 @@ export const AvatarInfoWidgetPetView: FC<AvatarInfoWidgetPetViewProps> = props =
|
||||
{
|
||||
const { avatarInfo = null, onClose = null } = props;
|
||||
const [ mode, setMode ] = useState(MODE_NORMAL);
|
||||
const [ respectsLeft, setRespectsLeft ] = useState(0);
|
||||
const { roomSession = null } = useRoom();
|
||||
const { petRespectRemaining = 0, respectPet = null } = useSessionInfo();
|
||||
|
||||
const canPickUp = useMemo(() =>
|
||||
{
|
||||
@ -54,18 +54,9 @@ export const AvatarInfoWidgetPetView: FC<AvatarInfoWidgetPetViewProps> = props =
|
||||
switch(name)
|
||||
{
|
||||
case 'respect':
|
||||
let newRespectsLeft = 0;
|
||||
respectPet(avatarInfo.id);
|
||||
|
||||
setRespectsLeft(prevValue =>
|
||||
{
|
||||
newRespectsLeft = (prevValue - 1);
|
||||
|
||||
return newRespectsLeft;
|
||||
});
|
||||
|
||||
GetSessionDataManager().givePetRespect(avatarInfo.id);
|
||||
|
||||
if(newRespectsLeft > 0) hideMenu = false;
|
||||
if((petRespectRemaining - 1) >= 1) hideMenu = false;
|
||||
break;
|
||||
case 'treat':
|
||||
SendMessageComposer(new PetRespectComposer(avatarInfo.id));
|
||||
@ -98,8 +89,6 @@ export const AvatarInfoWidgetPetView: FC<AvatarInfoWidgetPetViewProps> = props =
|
||||
|
||||
return MODE_NORMAL;
|
||||
});
|
||||
|
||||
setRespectsLeft(avatarInfo.respectsPetLeft);
|
||||
}, [ avatarInfo ]);
|
||||
|
||||
return (
|
||||
@ -107,9 +96,9 @@ export const AvatarInfoWidgetPetView: FC<AvatarInfoWidgetPetViewProps> = props =
|
||||
<ContextMenuHeaderView>
|
||||
{ avatarInfo.name }
|
||||
</ContextMenuHeaderView>
|
||||
{ (mode === MODE_NORMAL) && (respectsLeft > 0) &&
|
||||
{ (mode === MODE_NORMAL) && (petRespectRemaining > 0) &&
|
||||
<ContextMenuListItemView onClick={ event => processAction('respect') }>
|
||||
{ LocalizeText('infostand.button.petrespect', [ 'count' ], [ respectsLeft.toString() ]) }
|
||||
{ LocalizeText('infostand.button.petrespect', [ 'count' ], [ petRespectRemaining.toString() ]) }
|
||||
</ContextMenuListItemView> }
|
||||
{ (mode === MODE_SADDLED_UP) &&
|
||||
<>
|
||||
@ -117,9 +106,9 @@ export const AvatarInfoWidgetPetView: FC<AvatarInfoWidgetPetViewProps> = props =
|
||||
<ContextMenuListItemView onClick={ event => processAction('mount') }>
|
||||
{ LocalizeText('infostand.button.mount') }
|
||||
</ContextMenuListItemView> }
|
||||
{ (respectsLeft > 0) &&
|
||||
{ (petRespectRemaining > 0) &&
|
||||
<ContextMenuListItemView onClick={ event => processAction('respect') }>
|
||||
{ LocalizeText('infostand.button.petrespect', [ 'count' ], [ respectsLeft.toString() ]) }
|
||||
{ LocalizeText('infostand.button.petrespect', [ 'count' ], [ petRespectRemaining.toString() ]) }
|
||||
</ContextMenuListItemView> }
|
||||
</> }
|
||||
{ (mode === MODE_RIDING) &&
|
||||
@ -127,14 +116,14 @@ export const AvatarInfoWidgetPetView: FC<AvatarInfoWidgetPetViewProps> = props =
|
||||
<ContextMenuListItemView onClick={ event => processAction('dismount') }>
|
||||
{ LocalizeText('infostand.button.dismount') }
|
||||
</ContextMenuListItemView>
|
||||
{ (respectsLeft > 0) &&
|
||||
{ (petRespectRemaining > 0) &&
|
||||
<ContextMenuListItemView onClick={ event => processAction('respect') }>
|
||||
{ LocalizeText('infostand.button.petrespect', [ 'count' ], [ respectsLeft.toString() ]) }
|
||||
{ LocalizeText('infostand.button.petrespect', [ 'count' ], [ petRespectRemaining.toString() ]) }
|
||||
</ContextMenuListItemView> }
|
||||
</> }
|
||||
{ (mode === MODE_MONSTER_PLANT) && !avatarInfo.dead && ((avatarInfo.energy / avatarInfo.maximumEnergy) < 0.98) &&
|
||||
<ContextMenuListItemView onClick={ event => processAction('treat') }>
|
||||
{ LocalizeText('infostand.button.treat') }
|
||||
{ LocalizeText('infostand.button.pettreat') }
|
||||
</ContextMenuListItemView> }
|
||||
{ canPickUp &&
|
||||
<ContextMenuListItemView onClick={ event => processAction('pick_up') }>
|
||||
|
@ -89,8 +89,15 @@ export const ChatInputView: FC<{}> = props =>
|
||||
|
||||
if(text.length <= maxChatLength)
|
||||
{
|
||||
setChatValue('');
|
||||
sendChat(text, chatType, recipientName, chatStyleId);
|
||||
if(/%CC%/g.test(encodeURIComponent(text)))
|
||||
{
|
||||
setChatValue('');
|
||||
}
|
||||
else
|
||||
{
|
||||
setChatValue('');
|
||||
sendChat(text, chatType, recipientName, chatStyleId);
|
||||
}
|
||||
}
|
||||
|
||||
setChatValue(append);
|
||||
@ -141,7 +148,7 @@ export const ChatInputView: FC<{}> = props =>
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
}, [ floodBlocked, inputRef, chatModeIdWhisper, anotherInputHasFocus, setInputFocus, checkSpecialKeywordForInput, sendChatValue ]);
|
||||
|
||||
useUiEvent<RoomWidgetUpdateChatInputContentEvent>(RoomWidgetUpdateChatInputContentEvent.CHAT_INPUT_CONTENT, event =>
|
||||
|
@ -1,14 +1,24 @@
|
||||
import { FC } from 'react';
|
||||
import { CreateLinkEvent, LocalizeText } from '../../../../api';
|
||||
import { attemptItemPlacement, CreateLinkEvent, LocalizeText } from '../../../../api';
|
||||
import { Button, Column, Flex, LayoutGiftTagView, LayoutImage, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../../common';
|
||||
import { useFurniturePresentWidget } from '../../../../hooks';
|
||||
import { useFurniturePresentWidget, useInventoryFurni } from '../../../../hooks';
|
||||
|
||||
export const FurnitureGiftOpeningView: FC<{}> = props =>
|
||||
{
|
||||
const { objectId = -1, classId = -1, itemType = null, text = null, isOwnerOfFurniture = false, senderName = null, senderFigure = null, placedItemId = -1, placedItemType = null, placedInRoom = false, imageUrl = null, openPresent = null, onClose = null } = useFurniturePresentWidget();
|
||||
const { groupItems = [] } = useInventoryFurni();
|
||||
|
||||
if(objectId === -1) return null;
|
||||
|
||||
const place = (itemId: number) =>
|
||||
{
|
||||
const groupItem = groupItems.find(group => (group.getItemById(itemId)?.id === itemId));
|
||||
|
||||
if(groupItem) attemptItemPlacement(groupItem);
|
||||
|
||||
onClose();
|
||||
}
|
||||
|
||||
return (
|
||||
<NitroCardView className="nitro-gift-opening" theme="primary-slim">
|
||||
<NitroCardHeaderView headerText={ LocalizeText(senderName ? 'widget.furni.present.window.title_from' : 'widget.furni.present.window.title', [ 'name' ], [ senderName ]) } onCloseClick={ onClose } />
|
||||
@ -45,7 +55,7 @@ export const FurnitureGiftOpeningView: FC<{}> = props =>
|
||||
<Button fullWidth onClick={ null }>
|
||||
{ LocalizeText('widget.furni.present.put_in_inventory') }
|
||||
</Button> }
|
||||
<Button fullWidth variant="success" onClick={ null }>
|
||||
<Button fullWidth variant="success" onClick={ event => place(placedItemId) }>
|
||||
{ LocalizeText(placedInRoom ? 'widget.furni.present.keep_in_room' : 'widget.furni.present.place_in_room') }
|
||||
</Button>
|
||||
</Flex>
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { FurnitureStackHeightComposer } from '@nitrots/nitro-renderer';
|
||||
import { FC } from 'react';
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import ReactSlider from 'react-slider';
|
||||
import { LocalizeText, SendMessageComposer } from '../../../../api';
|
||||
import { Button, Column, Flex, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../../common';
|
||||
@ -8,6 +8,23 @@ import { useFurnitureStackHeightWidget } from '../../../../hooks';
|
||||
export const FurnitureStackHeightView: FC<{}> = props =>
|
||||
{
|
||||
const { objectId = -1, height = 0, maxHeight = 40, onClose = null, updateHeight = null } = useFurnitureStackHeightWidget();
|
||||
const [ tempHeight, setTempHeight ] = useState('');
|
||||
|
||||
const updateTempHeight = (value: string) =>
|
||||
{
|
||||
setTempHeight(value);
|
||||
|
||||
const newValue = parseFloat(value);
|
||||
|
||||
if(isNaN(newValue) || (newValue === height)) return;
|
||||
|
||||
updateHeight(newValue);
|
||||
}
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
setTempHeight(height.toString());
|
||||
}, [ height ]);
|
||||
|
||||
if(objectId === -1) return null;
|
||||
|
||||
@ -25,7 +42,7 @@ export const FurnitureStackHeightView: FC<{}> = props =>
|
||||
value={ height }
|
||||
onChange={ event => updateHeight(event) }
|
||||
renderThumb={ (props, state) => <div { ...props }>{ state.valueNow }</div> } />
|
||||
<input className="show-number-arrows" type="number" min={ 0 } max={ maxHeight } value={ height } onChange={ event => updateHeight(parseFloat(event.target.value)) } />
|
||||
<input className="show-number-arrows" style={ { width: 50 } } type="number" min={ 0 } max={ maxHeight } value={ tempHeight } onChange={ event => updateTempHeight(event.target.value) } />
|
||||
</Flex>
|
||||
<Column gap={ 1 }>
|
||||
<Button onClick={ event => SendMessageComposer(new FurnitureStackHeightComposer(objectId, -100)) }>
|
||||
|
@ -5,6 +5,8 @@ import { useFurnitureStickieWidget } from '../../../../hooks';
|
||||
|
||||
const STICKIE_COLORS = [ '9CCEFF','FF9CFF', '9CFF9C','FFFF33' ];
|
||||
const STICKIE_COLOR_NAMES = [ 'blue', 'pink', 'green', 'yellow' ];
|
||||
const STICKIE_TYPES = [ 'post_it','post_it_shakesp', 'post_it_dreams','post_it_xmas', 'post_it_vd', 'post_it_juninas' ];
|
||||
const STICKIE_TYPE_NAMES = [ 'post_it', 'shakesp', 'dreams', 'christmas', 'heart', 'juninas' ];
|
||||
|
||||
const getStickieColorName = (color: string) =>
|
||||
{
|
||||
@ -15,30 +17,42 @@ const getStickieColorName = (color: string) =>
|
||||
return STICKIE_COLOR_NAMES[index];
|
||||
}
|
||||
|
||||
const getStickieTypeName = (type: string) =>
|
||||
{
|
||||
let index = STICKIE_TYPES.indexOf(type);
|
||||
|
||||
if(index === -1) index = 0;
|
||||
|
||||
return STICKIE_TYPE_NAMES[index];
|
||||
}
|
||||
|
||||
export const FurnitureStickieView: FC<{}> = props =>
|
||||
{
|
||||
const { objectId = -1, color = '0', text = '', canModify = false, updateColor = null, updateText = null, trash = null, onClose = null } = useFurnitureStickieWidget();
|
||||
const { objectId = -1, color = '0', text = '', type = '', canModify = false, updateColor = null, updateText = null, trash = null, onClose = null } = useFurnitureStickieWidget();
|
||||
const [ isEditing, setIsEditing ] = useState(false);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
setIsEditing(false);
|
||||
}, [ objectId, color, text ]);
|
||||
}, [ objectId, color, text, type ]);
|
||||
|
||||
if(objectId === -1) return null;
|
||||
|
||||
return (
|
||||
<DraggableWindow handleSelector=".drag-handler" windowPosition={ DraggableWindowPosition.TOP_LEFT }>
|
||||
<div className={ 'nitro-stickie nitro-stickie-image stickie-' + getStickieColorName(color) }>
|
||||
<div className={ 'nitro-stickie nitro-stickie-image stickie-' + (type == 'post_it' ? getStickieColorName(color) : getStickieTypeName(type)) }>
|
||||
<div className="d-flex align-items-center stickie-header drag-handler">
|
||||
<div className="d-flex align-items-center flex-grow-1 h-100">
|
||||
{ canModify &&
|
||||
{ canModify &&
|
||||
<>
|
||||
<div className="nitro-stickie-image stickie-trash header-trash" onClick={ trash }></div>
|
||||
{ STICKIE_COLORS.map(color =>
|
||||
{
|
||||
return <div key={ color } className="stickie-color ms-1" onClick={ event => updateColor(color) } style={ { backgroundColor: ColorUtils.makeColorHex(color) } } />
|
||||
}) }
|
||||
{ type == 'post_it' &&
|
||||
<>
|
||||
{ STICKIE_COLORS.map(color =>
|
||||
{
|
||||
return <div key={ color } className="stickie-color ms-1" onClick={ event => updateColor(color) } style={ { backgroundColor: ColorUtils.makeColorHex(color) } } />
|
||||
}) }
|
||||
</> }
|
||||
</> }
|
||||
</div>
|
||||
<div className="d-flex align-items-center nitro-stickie-image stickie-close header-close" onClick={ onClose }></div>
|
||||
|
@ -161,6 +161,26 @@
|
||||
background-position: -2px -184px;
|
||||
}
|
||||
|
||||
&.stickie-christmas {
|
||||
background-image: url("../../../../assets/images/room-widgets/stickie-widget/stickie-christmas.png");
|
||||
}
|
||||
|
||||
&.stickie-shakesp {
|
||||
background-image: url("../../../../assets/images/room-widgets/stickie-widget/stickie-shakesp.png");
|
||||
}
|
||||
|
||||
&.stickie-dreams {
|
||||
background-image: url("../../../../assets/images/room-widgets/stickie-widget/stickie-dreams.png");
|
||||
}
|
||||
|
||||
&.stickie-heart {
|
||||
background-image: url("../../../../assets/images/room-widgets/stickie-widget/stickie-heart.png");
|
||||
}
|
||||
|
||||
&.stickie-juninas {
|
||||
background-image: url("../../../../assets/images/room-widgets/stickie-widget/stickie-juninas.png");
|
||||
}
|
||||
|
||||
&.stickie-close {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
@ -202,7 +222,7 @@
|
||||
font-weight: bold;
|
||||
font-size: 16px;
|
||||
text-shadow: 0px 1px white;
|
||||
|
||||
|
||||
&.engraving-lock-3 {
|
||||
background-position: 0px -210px;
|
||||
color: #614110;
|
||||
@ -262,7 +282,7 @@
|
||||
|
||||
.youtube-video-container {
|
||||
//min-height: 366px;
|
||||
|
||||
|
||||
.empty-video {
|
||||
background-color: black;
|
||||
color: white;
|
||||
|
@ -19,7 +19,7 @@ export const WiredBaseView: FC<PropsWithChildren<WiredBaseViewProps>> = props =>
|
||||
const [ wiredName, setWiredName ] = useState<string>(null);
|
||||
const [ wiredDescription, setWiredDescription ] = useState<string>(null);
|
||||
const [ needsSave, setNeedsSave ] = useState<boolean>(false);
|
||||
const { trigger = null, setTrigger = null, setIntParams = null, setStringParam = null, setFurniIds = null, saveWired = null } = useWired();
|
||||
const { trigger = null, setTrigger = null, setIntParams = null, setStringParam = null, setFurniIds = null, setAllowsFurni = null, saveWired = null } = useWired();
|
||||
|
||||
const onClose = () => setTrigger(null);
|
||||
|
||||
@ -83,6 +83,11 @@ export const WiredBaseView: FC<PropsWithChildren<WiredBaseViewProps>> = props =>
|
||||
}
|
||||
}, [ trigger, hasSpecialInput, requiresFurni, setIntParams, setStringParam, setFurniIds ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
setAllowsFurni(requiresFurni);
|
||||
}, [ requiresFurni, setAllowsFurni ]);
|
||||
|
||||
return (
|
||||
<NitroCardView uniqueKey="nitro-wired" className="nitro-wired" theme="primary-slim">
|
||||
<NitroCardHeaderView headerText={ LocalizeText('wiredfurni.title') } onCloseClick={ onClose } />
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import { LocalizeText, WiredFurniType, WIRED_STRING_DELIMETER } from '../../../../api';
|
||||
import { GetConfiguration, LocalizeText, WiredFurniType, WIRED_STRING_DELIMETER } from '../../../../api';
|
||||
import { Column, Flex, Text } from '../../../../common';
|
||||
import { useWired } from '../../../../hooks';
|
||||
import { WiredActionBaseView } from './WiredActionBaseView';
|
||||
@ -35,7 +35,7 @@ export const WiredActionBotTalkToAvatarView: FC<{}> = props =>
|
||||
</Column>
|
||||
<Column gap={ 1 }>
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.message') }</Text>
|
||||
<input type="text" className="form-control form-control-sm" maxLength={ 64 } value={ message } onChange={ event => setMessage(event.target.value) } />
|
||||
<input type="text" className="form-control form-control-sm" maxLength={ GetConfiguration<number>('wired.action.bot.talk.to.avatar.max.length', 64) } value={ message } onChange={ event => setMessage(event.target.value) } />
|
||||
</Column>
|
||||
<Column gap={ 1 }>
|
||||
<Flex alignItems="center" gap={ 1 }>
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import { LocalizeText, WiredFurniType, WIRED_STRING_DELIMETER } from '../../../../api';
|
||||
import { GetConfiguration, LocalizeText, WiredFurniType, WIRED_STRING_DELIMETER } from '../../../../api';
|
||||
import { Column, Flex, Text } from '../../../../common';
|
||||
import { useWired } from '../../../../hooks';
|
||||
import { WiredActionBaseView } from './WiredActionBaseView';
|
||||
@ -35,7 +35,7 @@ export const WiredActionBotTalkView: FC<{}> = props =>
|
||||
</Column>
|
||||
<Column gap={ 1 }>
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.message') }</Text>
|
||||
<input type="text" className="form-control form-control-sm" maxLength={ 64 } value={ message } onChange={ event => setMessage(event.target.value) } />
|
||||
<input type="text" className="form-control form-control-sm" maxLength={ GetConfiguration<number>('wired.action.bot.talk.max.length', 64) } value={ message } onChange={ event => setMessage(event.target.value) } />
|
||||
</Column>
|
||||
<Column gap={ 1 }>
|
||||
<Flex alignItems="center" gap={ 1 }>
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import { LocalizeText, WiredFurniType } from '../../../../api';
|
||||
import { GetConfiguration, LocalizeText, WiredFurniType } from '../../../../api';
|
||||
import { Column, Text } from '../../../../common';
|
||||
import { useWired } from '../../../../hooks';
|
||||
import { WiredActionBaseView } from './WiredActionBaseView';
|
||||
@ -20,7 +20,7 @@ export const WiredActionChatView: FC<{}> = props =>
|
||||
<WiredActionBaseView requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_NONE } hasSpecialInput={ true } save={ save }>
|
||||
<Column gap={ 1 }>
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.message') }</Text>
|
||||
<input type="text" className="form-control form-control-sm" value={ message } onChange={ event => setMessage(event.target.value) } maxLength={ 100 } />
|
||||
<input type="text" className="form-control form-control-sm" value={ message } onChange={ event => setMessage(event.target.value) } maxLength={ GetConfiguration<number>('wired.action.chat.max.length', 100) } />
|
||||
</Column>
|
||||
</WiredActionBaseView>
|
||||
);
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import { LocalizeText, WiredFurniType } from '../../../../api';
|
||||
import { GetConfiguration, LocalizeText, WiredFurniType } from '../../../../api';
|
||||
import { Column, Text } from '../../../../common';
|
||||
import { useWired } from '../../../../hooks';
|
||||
import { WiredActionBaseView } from './WiredActionBaseView';
|
||||
@ -20,7 +20,7 @@ export const WiredActionKickFromRoomView: FC<{}> = props =>
|
||||
<WiredActionBaseView requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_NONE } hasSpecialInput={ true } save={ save }>
|
||||
<Column gap={ 1 }>
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.message') }</Text>
|
||||
<input type="text" className="form-control form-control-sm" value={ message } onChange={ event => setMessage(event.target.value) } maxLength={ 100 } />
|
||||
<input type="text" className="form-control form-control-sm" value={ message } onChange={ event => setMessage(event.target.value) } maxLength={ GetConfiguration<number>('wired.action.kick.from.room.max.length', 100) } />
|
||||
</Column>
|
||||
</WiredActionBaseView>
|
||||
);
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import ReactSlider from 'react-slider';
|
||||
import { LocalizeText, WiredFurniType } from '../../../../api';
|
||||
import { GetConfiguration, LocalizeText, WiredFurniType } from '../../../../api';
|
||||
import { Column, Text } from '../../../../common';
|
||||
import { useWired } from '../../../../hooks';
|
||||
import { WiredActionBaseView } from './WiredActionBaseView';
|
||||
@ -36,7 +36,7 @@ export const WiredActionMuteUserView: FC<{}> = props =>
|
||||
</Column>
|
||||
<Column gap={ 1 }>
|
||||
<Text bold>{ LocalizeText('wiredfurni.params.message') }</Text>
|
||||
<input type="text" className="form-control form-control-sm" value={ message } onChange={ event => setMessage(event.target.value) } maxLength={ 100 } />
|
||||
<input type="text" className="form-control form-control-sm" value={ message } onChange={ event => setMessage(event.target.value) } maxLength={ GetConfiguration<number>('wired.action.mute.user.max.length', 100) } />
|
||||
</Column>
|
||||
</WiredActionBaseView>
|
||||
);
|
||||
|
@ -835,7 +835,7 @@ const useCatalogState = () =>
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!isVisible || !rootNode || !requestedPage.current) return;
|
||||
if(!isVisible || !rootNode || !offersToNodes || !requestedPage.current) return;
|
||||
|
||||
switch(requestedPage.current.requestType)
|
||||
{
|
||||
@ -868,12 +868,19 @@ const useCatalogState = () =>
|
||||
requestedPage.current.resetRequest();
|
||||
return;
|
||||
}
|
||||
}, [ isVisible, rootNode, currentPage, activateNode, openPageById, openPageByOfferId, openPageByName ]);
|
||||
}, [ isVisible, rootNode, offersToNodes, currentPage, activateNode, openPageById, openPageByOfferId, openPageByName ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!searchResult && currentPage && (currentPage.pageId === -1)) openPageById(previousPageId);
|
||||
}, [ searchResult, currentPage, previousPageId, openPageById ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!currentOffer) return;
|
||||
|
||||
setPurchaseOptions({ quantity: 1, extraData: null, extraParamRequired: false, previewStuffData: null });
|
||||
}, [ currentOffer ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
|
@ -24,7 +24,7 @@ const useFurnitureStackHeightWidgetState = () =>
|
||||
const updateHeight = (height: number, server: boolean = false) =>
|
||||
{
|
||||
if(!height) height = 0;
|
||||
|
||||
|
||||
height = Math.abs(height);
|
||||
|
||||
if(!server) ((height > MAX_HEIGHT) && (height = MAX_HEIGHT));
|
||||
|
@ -10,6 +10,7 @@ const useFurnitureStickieWidgetState = () =>
|
||||
const [ category, setCategory ] = useState(-1);
|
||||
const [ color, setColor ] = useState('0');
|
||||
const [ text, setText ] = useState('');
|
||||
const [ type, setType ] = useState('');
|
||||
const [ canModify, setCanModify ] = useState(false);
|
||||
|
||||
const onClose = () =>
|
||||
@ -18,6 +19,7 @@ const useFurnitureStickieWidgetState = () =>
|
||||
setCategory(-1);
|
||||
setColor('0');
|
||||
setText('');
|
||||
setType('');
|
||||
setCanModify(false);
|
||||
}
|
||||
|
||||
@ -44,7 +46,7 @@ const useFurnitureStickieWidgetState = () =>
|
||||
const roomObject = GetRoomEngine().getRoomObject(event.roomId, event.objectId, event.category);
|
||||
|
||||
if(!roomObject) return;
|
||||
|
||||
|
||||
const data = roomObject.model.getValue<string>(RoomObjectVariable.FURNITURE_ITEMDATA);
|
||||
|
||||
if(data.length < 6) return;
|
||||
@ -66,6 +68,7 @@ const useFurnitureStickieWidgetState = () =>
|
||||
setCategory(event.category);
|
||||
setColor(color || '0');
|
||||
setText(text || '');
|
||||
setType(roomObject.type || 'post_it');
|
||||
setCanModify(GetRoomSession().isRoomOwner || GetSessionDataManager().isModerator || IsOwnerOfFurniture(roomObject));
|
||||
});
|
||||
|
||||
@ -76,7 +79,7 @@ const useFurnitureStickieWidgetState = () =>
|
||||
onClose();
|
||||
});
|
||||
|
||||
return { objectId, color, text, canModify, updateColor, updateText, trash, onClose };
|
||||
return { objectId, color, text, type, canModify, updateColor, updateText, trash, onClose };
|
||||
}
|
||||
|
||||
export const useFurnitureStickieWidget = useFurnitureStickieWidgetState;
|
||||
|
@ -1,14 +1,18 @@
|
||||
import { FigureUpdateEvent, RoomUnitChatStyleComposer, UserInfoDataParser, UserInfoEvent, UserSettingsEvent } from '@nitrots/nitro-renderer';
|
||||
import { useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useBetween } from 'use-between';
|
||||
import { SendMessageComposer } from '../../api';
|
||||
import { GetLocalStorage, GetSessionDataManager, SendMessageComposer } from '../../api';
|
||||
import { useMessageEvent } from '../events';
|
||||
import { useLocalStorage } from '../useLocalStorage';
|
||||
|
||||
const useSessionInfoState = () =>
|
||||
{
|
||||
const [ userInfo, setUserInfo ] = useState<UserInfoDataParser>(null);
|
||||
const [ userFigure, setUserFigure ] = useState<string>(null);
|
||||
const [ chatStyleId, setChatStyleId ] = useState<number>(0);
|
||||
const [ userRespectRemaining, setUserRespectRemaining ] = useState<number>(0);
|
||||
const [ petRespectRemaining, setPetRespectRemaining ] = useState<number>(0);
|
||||
const [ screenSize, setScreenSize ] = useLocalStorage('nitro.screensize', { width: window.innerWidth, height: window.innerHeight });
|
||||
|
||||
const updateChatStyleId = (styleId: number) =>
|
||||
{
|
||||
@ -17,12 +21,28 @@ const useSessionInfoState = () =>
|
||||
SendMessageComposer(new RoomUnitChatStyleComposer(styleId));
|
||||
}
|
||||
|
||||
const respectUser = (userId: number) =>
|
||||
{
|
||||
GetSessionDataManager().giveRespect(userId);
|
||||
|
||||
setUserRespectRemaining(GetSessionDataManager().respectsLeft);
|
||||
}
|
||||
|
||||
const respectPet = (petId: number) =>
|
||||
{
|
||||
GetSessionDataManager().givePetRespect(petId);
|
||||
|
||||
setPetRespectRemaining(GetSessionDataManager().respectsPetLeft);
|
||||
}
|
||||
|
||||
useMessageEvent<UserInfoEvent>(UserInfoEvent, event =>
|
||||
{
|
||||
const parser = event.getParser();
|
||||
|
||||
setUserInfo(parser.userInfo);
|
||||
setUserFigure(parser.userInfo.figure);
|
||||
setUserRespectRemaining(parser.userInfo.respectsRemaining);
|
||||
setPetRespectRemaining(parser.userInfo.respectsPetRemaining);
|
||||
});
|
||||
|
||||
useMessageEvent<FigureUpdateEvent>(FigureUpdateEvent, event =>
|
||||
@ -39,7 +59,35 @@ const useSessionInfoState = () =>
|
||||
setChatStyleId(parser.chatType);
|
||||
});
|
||||
|
||||
return { userInfo, userFigure, chatStyleId, updateChatStyleId };
|
||||
useEffect(() =>
|
||||
{
|
||||
const currentScreenSize = <{ width: number, height: number }>GetLocalStorage('nitro.screensize');
|
||||
|
||||
if(currentScreenSize && ((currentScreenSize.width !== window.innerWidth) || (currentScreenSize.height !== window.innerHeight)))
|
||||
{
|
||||
let i = window.localStorage.length;
|
||||
|
||||
while(i > 0)
|
||||
{
|
||||
const key = window.localStorage.key(i);
|
||||
|
||||
if(key && key.startsWith('nitro.window')) window.localStorage.removeItem(key);
|
||||
|
||||
i--;
|
||||
}
|
||||
}
|
||||
|
||||
const onResize = (event: UIEvent) => setScreenSize({ width: window.innerWidth, height: window.innerHeight });
|
||||
|
||||
window.addEventListener('resize', onResize);
|
||||
|
||||
return () =>
|
||||
{
|
||||
window.removeEventListener('resize', onResize);
|
||||
}
|
||||
}, [ setScreenSize ]);
|
||||
|
||||
return { userInfo, userFigure, chatStyleId, userRespectRemaining, petRespectRemaining, respectUser, respectPet, updateChatStyleId };
|
||||
}
|
||||
|
||||
export const useSessionInfo = () => useBetween(useSessionInfoState);
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { NitroLogger } from '@nitrots/nitro-renderer';
|
||||
import { Dispatch, SetStateAction, useState } from 'react';
|
||||
import { GetLocalStorage, SetLocalStorage } from '../api';
|
||||
|
||||
const useLocalStorageState = <T>(key: string, initialValue: T): [ T, Dispatch<SetStateAction<T>>] =>
|
||||
{
|
||||
@ -9,9 +10,9 @@ const useLocalStorageState = <T>(key: string, initialValue: T): [ T, Dispatch<Se
|
||||
|
||||
try
|
||||
{
|
||||
const item = window.localStorage.getItem(key);
|
||||
const item = GetLocalStorage<T>(key);
|
||||
|
||||
return item ? JSON.parse(item) : initialValue;
|
||||
return item ?? initialValue;
|
||||
}
|
||||
|
||||
catch(error)
|
||||
@ -28,7 +29,7 @@ const useLocalStorageState = <T>(key: string, initialValue: T): [ T, Dispatch<Se
|
||||
|
||||
setStoredValue(valueToStore);
|
||||
|
||||
if(typeof window !== 'undefined') window.localStorage.setItem(key, JSON.stringify(valueToStore));
|
||||
if(typeof window !== 'undefined') SetLocalStorage(key, valueToStore);
|
||||
}
|
||||
|
||||
catch(error)
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { ConditionDefinition, Triggerable, TriggerDefinition, UpdateActionMessageComposer, UpdateConditionMessageComposer, UpdateTriggerMessageComposer, WiredActionDefinition, WiredFurniActionEvent, WiredFurniConditionEvent, WiredFurniTriggerEvent, WiredSaveSuccessEvent } from '@nitrots/nitro-renderer';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useBetween } from 'use-between';
|
||||
import { IsOwnerOfFloorFurniture, LocalizeText, SendMessageComposer, WiredSelectionVisualizer } from '../../api';
|
||||
import { IsOwnerOfFloorFurniture, LocalizeText, SendMessageComposer, WiredFurniType, WiredSelectionVisualizer } from '../../api';
|
||||
import { useMessageEvent } from '../events';
|
||||
import { useNotification } from '../notification';
|
||||
|
||||
@ -12,6 +12,7 @@ const useWiredState = () =>
|
||||
const [ stringParam, setStringParam ] = useState<string>('');
|
||||
const [ furniIds, setFurniIds ] = useState<number[]>([]);
|
||||
const [ actionDelay, setActionDelay ] = useState<number>(0);
|
||||
const [ allowsFurni, setAllowsFurni ] = useState<number>(WiredFurniType.STUFF_SELECTION_OPTION_NONE);
|
||||
const { showConfirm = null } = useNotification();
|
||||
|
||||
const saveWired = () =>
|
||||
@ -51,7 +52,7 @@ const useWiredState = () =>
|
||||
|
||||
const selectObjectForWired = (objectId: number, category: number) =>
|
||||
{
|
||||
if(!trigger) return;
|
||||
if(!trigger || !allowsFurni) return;
|
||||
|
||||
if(objectId <= 0) return;
|
||||
|
||||
@ -122,10 +123,11 @@ const useWiredState = () =>
|
||||
|
||||
return [];
|
||||
});
|
||||
setAllowsFurni(WiredFurniType.STUFF_SELECTION_OPTION_NONE);
|
||||
}
|
||||
}, [ trigger ]);
|
||||
|
||||
return { trigger, setTrigger, intParams, setIntParams, stringParam, setStringParam, furniIds, setFurniIds, actionDelay, setActionDelay, saveWired, selectObjectForWired };
|
||||
return { trigger, setTrigger, intParams, setIntParams, stringParam, setStringParam, furniIds, setFurniIds, actionDelay, setActionDelay, setAllowsFurni, saveWired, selectObjectForWired };
|
||||
}
|
||||
|
||||
export const useWired = () => useBetween(useWiredState);
|
||||
|
Loading…
Reference in New Issue
Block a user