Merge branch '@fix/fixes' into 'fix-monsterplant-info-like-habbo'

# Conflicts:
#   src/components/room/widgets/avatar-info/infostand/InfoStandWidgetPetView.tsx
This commit is contained in:
Bill 2022-08-17 23:32:31 +00:00
commit 79b29944f9
41 changed files with 452 additions and 412 deletions

View File

@ -1,6 +1,7 @@
{ {
"name": "nitro-react", "name": "nitro-react",
"version": "2.1.0", "version": "2.1.0",
"homepage": ".",
"private": true, "private": true,
"scripts": { "scripts": {
"start": "cross-env SKIP_PREFLIGHT_CHECK=true BROWSER=none IMAGE_INLINE_SIZE_LIMIT=100000 craco --openssl-legacy-provider start", "start": "cross-env SKIP_PREFLIGHT_CHECK=true BROWSER=none IMAGE_INLINE_SIZE_LIMIT=100000 craco --openssl-legacy-provider start",

View File

@ -15,6 +15,7 @@
<meta name="theme-color" content="#000000" /> <meta name="theme-color" content="#000000" />
<meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"> <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<base href="./">
<title>Nitro</title> <title>Nitro</title>
</head> </head>
<body> <body>

View File

@ -16,6 +16,11 @@
"badge.descriptions.enabled": true, "badge.descriptions.enabled": true,
"motto.max.length": 38, "motto.max.length": 38,
"bot.name.max.length": 15, "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": [ "navigator.room.models": [
{ "clubLevel": 0, "tileSize": 104, "name": "a" }, { "clubLevel": 0, "tileSize": 104, "name": "a" },
{ "clubLevel": 0, "tileSize": 94, "name": "b" }, { "clubLevel": 0, "tileSize": 94, "name": "b" },

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

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

View File

@ -53,7 +53,7 @@
position: absolute; position: absolute;
width: 110px; width: 110px;
height: 110px; height: 110px;
margin-top: 38px; margin-top: 30px;
margin-left: 3px; margin-left: 3px;
} }
} }
@ -110,8 +110,7 @@
.gift-incognito { .gift-incognito {
width: 37px; width: 37px;
height: 48px; height: 48px;
background: url("../assets/images/gift/incognito.png") center background: url("../assets/images/gift/incognito.png") center no-repeat;
no-repeat;
} }
.gift-avatar { .gift-avatar {
@ -168,23 +167,27 @@
} }
@-webkit-keyframes sk-bouncedelay { @-webkit-keyframes sk-bouncedelay {
0%, 0%,
80%, 80%,
100% { 100% {
-webkit-transform: scale(0); -webkit-transform: scale(0);
} }
40% { 40% {
-webkit-transform: scale(1); -webkit-transform: scale(1);
} }
} }
@keyframes sk-bouncedelay { @keyframes sk-bouncedelay {
0%, 0%,
80%, 80%,
100% { 100% {
-webkit-transform: scale(0); -webkit-transform: scale(0);
transform: scale(0); transform: scale(0);
} }
40% { 40% {
-webkit-transform: scale(1); -webkit-transform: scale(1);
transform: scale(1); transform: scale(1);
@ -195,8 +198,7 @@
position: relative; position: relative;
width: 110px; width: 110px;
height: 110px; height: 110px;
background: url("../assets/images/navigator/thumbnail_placeholder.png") background: url("../assets/images/navigator/thumbnail_placeholder.png") no-repeat center;
no-repeat center;
background-color: rgba($black, 0.125); background-color: rgba($black, 0.125);
} }
@ -388,15 +390,13 @@
content: ""; content: "";
width: 100%; width: 100%;
height: 100%; height: 100%;
background: url("../assets/images/unique/grid-bg-glass.png") center background: url("../assets/images/unique/grid-bg-glass.png") center no-repeat;
no-repeat;
bottom: 0; bottom: 0;
z-index: 4; z-index: 4;
} }
&.sold-out:after { &.sold-out:after {
background: url("../assets/images/unique/grid-bg-sold-out.png") center background: url("../assets/images/unique/grid-bg-sold-out.png") center no-repeat,
no-repeat,
url("../assets/images/unique/grid-bg-glass.png") center no-repeat; url("../assets/images/unique/grid-bg-glass.png") center no-repeat;
} }
@ -408,8 +408,7 @@
bottom: 1px; bottom: 1px;
width: 100%; width: 100%;
height: 9px; height: 9px;
background: url("../assets/images/unique/grid-count-bg.png") center background: url("../assets/images/unique/grid-count-bg.png") center no-repeat;
no-repeat;
z-index: 3; z-index: 3;
} }
} }
@ -451,8 +450,7 @@
.unique-complete-plate { .unique-complete-plate {
width: 170px; width: 170px;
height: 29px; height: 29px;
background: url("../assets/images/unique/catalog-info-amount-bg.png") background: url("../assets/images/unique/catalog-info-amount-bg.png") no-repeat center;
no-repeat center;
z-index: 1; z-index: 1;
padding-top: 3px; padding-top: 3px;
@ -548,12 +546,10 @@
z-index: 1; z-index: 1;
transition: all 1s; transition: all 1s;
border-radius: calc(#{$border-radius} / 2); border-radius: calc(#{$border-radius} / 2);
background: repeating-linear-gradient( background: repeating-linear-gradient($tertiary,
$tertiary,
$tertiary 50%, $tertiary 50%,
$quaternary 50%, $quaternary 50%,
$quaternary 100% $quaternary 100%);
);
} }
.nitro-progress-bar-text { .nitro-progress-bar-text {

View File

@ -1,4 +1,4 @@
import { FC, useCallback, useEffect, useState } from 'react'; import { FC, useEffect, useState } from 'react';
import { AvatarEditorGridPartItem, GetConfiguration } from '../../../../api'; import { AvatarEditorGridPartItem, GetConfiguration } from '../../../../api';
import { LayoutCurrencyIcon, LayoutGridItem, LayoutGridItemProps } from '../../../../common'; import { LayoutCurrencyIcon, LayoutGridItem, LayoutGridItemProps } from '../../../../common';
import { AvatarEditorIcon } from '../AvatarEditorIcon'; import { AvatarEditorIcon } from '../AvatarEditorIcon';
@ -15,20 +15,14 @@ export const AvatarEditorFigureSetItemView: FC<AvatarEditorFigureSetItemViewProp
const hcDisabled = GetConfiguration<boolean>('hc.disabled', false); const hcDisabled = GetConfiguration<boolean>('hc.disabled', false);
const rerender = useCallback(() =>
{
setUpdateId(prevValue => (prevValue + 1));
}, []);
useEffect(() => useEffect(() =>
{ {
const rerender = () => setUpdateId(prevValue => (prevValue + 1));
partItem.notify = rerender; partItem.notify = rerender;
return () => return () => partItem.notify = null;
{ }, [ partItem ]);
partItem.notify = null;
}
}, [ partItem, rerender ]);
return ( return (
<LayoutGridItem itemImage={ (partItem.isClear ? undefined : partItem.imageUrl) } itemActive={ partItem.isSelected } { ...rest }> <LayoutGridItem itemImage={ (partItem.isClear ? undefined : partItem.imageUrl) } itemActive={ partItem.isSelected } { ...rest }>

View File

@ -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 { AvatarEditorGridPartItem, CategoryData, IAvatarEditorCategoryModel } from '../../../../api';
import { AutoGrid } from '../../../../common'; import { AutoGrid } from '../../../../common';
import { AvatarEditorFigureSetItemView } from './AvatarEditorFigureSetItemView'; import { AvatarEditorFigureSetItemView } from './AvatarEditorFigureSetItemView';
@ -13,6 +13,7 @@ export interface AvatarEditorFigureSetViewProps
export const AvatarEditorFigureSetView: FC<AvatarEditorFigureSetViewProps> = props => export const AvatarEditorFigureSetView: FC<AvatarEditorFigureSetViewProps> = props =>
{ {
const { model = null, category = null, setMaxPaletteCount = null } = props; const { model = null, category = null, setMaxPaletteCount = null } = props;
const elementRef = useRef<HTMLDivElement>(null);
const selectPart = useCallback((item: AvatarEditorGridPartItem) => const selectPart = useCallback((item: AvatarEditorGridPartItem) =>
{ {
@ -27,8 +28,15 @@ export const AvatarEditorFigureSetView: FC<AvatarEditorFigureSetViewProps> = pro
setMaxPaletteCount(partItem.maxColorIndex || 1); setMaxPaletteCount(partItem.maxColorIndex || 1);
}, [ model, category, setMaxPaletteCount ]); }, [ model, category, setMaxPaletteCount ]);
useEffect(() =>
{
if(!model || !category || !elementRef || !elementRef.current) return;
elementRef.current.scrollTop = 0;
}, [ model, category ]);
return ( return (
<AutoGrid columnCount={ 3 } columnMinHeight={ 50 }> <AutoGrid innerRef={ elementRef } columnCount={ 3 } columnMinHeight={ 50 }>
{ (category.parts.length > 0) && category.parts.map((item, index) => { (category.parts.length > 0) && category.parts.map((item, index) =>
<AvatarEditorFigureSetItemView key={ index } partItem={ item } onClick={ event => selectPart(item) } />) } <AvatarEditorFigureSetItemView key={ index } partItem={ item } onClick={ event => selectPart(item) } />) }
</AutoGrid> </AutoGrid>

View File

@ -1,4 +1,4 @@
import { FC, useCallback, useEffect, useState } from 'react'; import { FC, useEffect, useState } from 'react';
import { AvatarEditorGridColorItem, GetConfiguration } from '../../../../api'; import { AvatarEditorGridColorItem, GetConfiguration } from '../../../../api';
import { LayoutCurrencyIcon, LayoutGridItem, LayoutGridItemProps } from '../../../../common'; import { LayoutCurrencyIcon, LayoutGridItem, LayoutGridItemProps } from '../../../../common';
@ -14,17 +14,14 @@ export const AvatarEditorPaletteSetItem: FC<AvatarEditorPaletteSetItemProps> = p
const hcDisabled = GetConfiguration<boolean>('hc.disabled', false); const hcDisabled = GetConfiguration<boolean>('hc.disabled', false);
const rerender = useCallback(() =>
{
setUpdateId(prevValue => (prevValue + 1));
}, []);
useEffect(() => useEffect(() =>
{ {
const rerender = () => setUpdateId(prevValue => (prevValue + 1));
colorItem.notify = rerender; colorItem.notify = rerender;
return () => colorItem.notify = null; return () => colorItem.notify = null;
}); }, [ colorItem ]);
return ( return (
<LayoutGridItem itemHighlight itemColor={ colorItem.color } itemActive={ colorItem.isSelected } className="clear-bg" { ...rest }> <LayoutGridItem itemHighlight itemColor={ colorItem.color } itemActive={ colorItem.isSelected } className="clear-bg" { ...rest }>

View File

@ -1,4 +1,4 @@
import { FC, useCallback } from 'react'; import { FC, useCallback, useEffect, useRef } from 'react';
import { AvatarEditorGridColorItem, CategoryData, IAvatarEditorCategoryModel } from '../../../../api'; import { AvatarEditorGridColorItem, CategoryData, IAvatarEditorCategoryModel } from '../../../../api';
import { AutoGrid } from '../../../../common'; import { AutoGrid } from '../../../../common';
import { AvatarEditorPaletteSetItem } from './AvatarEditorPaletteSetItemView'; import { AvatarEditorPaletteSetItem } from './AvatarEditorPaletteSetItemView';
@ -14,6 +14,7 @@ export interface AvatarEditorPaletteSetViewProps
export const AvatarEditorPaletteSetView: FC<AvatarEditorPaletteSetViewProps> = props => export const AvatarEditorPaletteSetView: FC<AvatarEditorPaletteSetViewProps> = props =>
{ {
const { model = null, category = null, paletteSet = [], paletteIndex = -1 } = props; const { model = null, category = null, paletteSet = [], paletteIndex = -1 } = props;
const elementRef = useRef<HTMLDivElement>(null);
const selectColor = useCallback((item: AvatarEditorGridColorItem) => const selectColor = useCallback((item: AvatarEditorGridColorItem) =>
{ {
@ -24,8 +25,15 @@ export const AvatarEditorPaletteSetView: FC<AvatarEditorPaletteSetViewProps> = p
model.selectColor(category.name, index, paletteIndex); model.selectColor(category.name, index, paletteIndex);
}, [ model, category, paletteSet, paletteIndex ]); }, [ model, category, paletteSet, paletteIndex ]);
useEffect(() =>
{
if(!model || !category || !elementRef || !elementRef.current) return;
elementRef.current.scrollTop = 0;
}, [ model, category ]);
return ( return (
<AutoGrid gap={ 1 } columnCount={ 5 } columnMinWidth={ 30 }> <AutoGrid innerRef={ elementRef } gap={ 1 } columnCount={ 5 } columnMinWidth={ 30 }>
{ (paletteSet.length > 0) && paletteSet.map((item, index) => { (paletteSet.length > 0) && paletteSet.map((item, index) =>
<AvatarEditorPaletteSetItem key={ index } colorItem={ item } onClick={ event => selectColor(item) } />) } <AvatarEditorPaletteSetItem key={ index } colorItem={ item } onClick={ event => selectColor(item) } />) }
</AutoGrid> </AutoGrid>

View File

@ -103,11 +103,7 @@ export const CatalogPurchaseWidgetView: FC<CatalogPurchaseWidgetViewProps> = pro
{ {
if(!currentOffer) return; if(!currentOffer) return;
return () =>
{
setPurchaseState(CatalogPurchaseState.NONE); setPurchaseState(CatalogPurchaseState.NONE);
setPurchaseOptions({ quantity: 1, extraData: null, extraParamRequired: false, previewStuffData: null });
}
}, [ currentOffer, setPurchaseOptions ]); }, [ currentOffer, setPurchaseOptions ]);
useEffect(() => useEffect(() =>

View File

@ -1,5 +1,5 @@
import { GuideSessionGetRequesterRoomMessageComposer, GuideSessionInviteRequesterMessageComposer, GuideSessionMessageMessageComposer, GuideSessionRequesterRoomMessageEvent, GuideSessionResolvedMessageComposer } from '@nitrots/nitro-renderer'; 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 { GetSessionDataManager, GuideToolMessageGroup, LocalizeText, SendMessageComposer, TryVisitRoom } from '../../../api';
import { Base, Button, ButtonGroup, Column, Flex, LayoutAvatarImageView, Text } from '../../../common'; import { Base, Button, ButtonGroup, Column, Flex, LayoutAvatarImageView, Text } from '../../../common';
import { useMessageEvent } from '../../../hooks'; import { useMessageEvent } from '../../../hooks';
@ -16,10 +16,18 @@ interface GuideToolOngoingViewProps
export const GuideToolOngoingView: FC<GuideToolOngoingViewProps> = props => 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 { isGuide = false, userId = 0, userName = null, userFigure = null, isTyping = false, messageGroups = [] } = props;
const [ messageText, setMessageText ] = useState<string>(''); const [ messageText, setMessageText ] = useState<string>('');
useEffect(() =>
{
scrollDiv.current?.scrollIntoView({ block: 'end', behavior: 'smooth' });
}, [ messageGroups ]);
const visit = useCallback(() => const visit = useCallback(() =>
{ {
SendMessageComposer(new GuideSessionGetRequesterRoomMessageComposer()); SendMessageComposer(new GuideSessionGetRequesterRoomMessageComposer());
@ -101,6 +109,7 @@ export const GuideToolOngoingView: FC<GuideToolOngoingViewProps> = props =>
</Flex> </Flex>
); );
}) } }) }
<div ref={ scrollDiv } />
</Column> </Column>
</Column> </Column>
<Column gap={ 1 }> <Column gap={ 1 }>

View File

@ -71,8 +71,6 @@ export const HelpView: FC<{}> = props =>
setIsVisible(true); setIsVisible(true);
}, [ activeReport ]); }, [ activeReport ]);
if(!isVisible && !activeReport) return null;
const CurrentStepView = () => const CurrentStepView = () =>
{ {
if(activeReport) if(activeReport)
@ -97,6 +95,7 @@ export const HelpView: FC<{}> = props =>
return ( return (
<> <>
{ isVisible &&
<NitroCardView className="nitro-help" theme="primary-slim"> <NitroCardView className="nitro-help" theme="primary-slim">
<NitroCardHeaderView headerText={ LocalizeText('help.button.cfh') } onCloseClick={ onClose } /> <NitroCardHeaderView headerText={ LocalizeText('help.button.cfh') } onCloseClick={ onClose } />
<NitroCardContentView className="text-black"> <NitroCardContentView className="text-black">
@ -109,7 +108,7 @@ export const HelpView: FC<{}> = props =>
</Column> </Column>
</Grid> </Grid>
</NitroCardContentView> </NitroCardContentView>
</NitroCardView> </NitroCardView> }
<SanctionSatusView /> <SanctionSatusView />
<NameChangeView /> <NameChangeView />
</> </>

View File

@ -21,6 +21,7 @@ export const SelectReportedChatsView: FC<{}> = props =>
return messengerHistory.filter(chat => (chat.entityId === activeReport.reportedUserId) && (chat.type === ChatEntryType.TYPE_IM)); return messengerHistory.filter(chat => (chat.entityId === activeReport.reportedUserId) && (chat.type === ChatEntryType.TYPE_IM));
} }
return [];
}, [ activeReport, chatHistory, messengerHistory ]); }, [ activeReport, chatHistory, messengerHistory ]);
const selectChat = (chatEntry: IChatEntry) => const selectChat = (chatEntry: IChatEntry) =>
@ -62,7 +63,7 @@ export const SelectReportedChatsView: FC<{}> = props =>
<Text>{ LocalizeText('help.emergency.chat_report.description') }</Text> <Text>{ LocalizeText('help.emergency.chat_report.description') }</Text>
</Column> </Column>
<Column gap={ 1 } overflow="hidden"> <Column gap={ 1 } overflow="hidden">
{ !!!userChats.length && { !userChats || !userChats.length &&
<Text>{ LocalizeText('help.cfh.error.no_user_data') }</Text> } <Text>{ LocalizeText('help.cfh.error.no_user_data') }</Text> }
{ (userChats.length > 0) && { (userChats.length > 0) &&
<AutoGrid gap={ 1 } columnCount={ 1 } columnMinHeight={ 25 } overflow="auto"> <AutoGrid gap={ 1 } columnCount={ 1 } columnMinHeight={ 25 } overflow="auto">

View File

@ -17,13 +17,11 @@ export const NameChangeView:FC<{}> = props =>
const [ layout, setLayout ] = useState<string>(INIT); const [ layout, setLayout ] = useState<string>(INIT);
const [ newUsername, setNewUsername ] = useState<string>(''); const [ newUsername, setNewUsername ] = useState<string>('');
const onHelpNameChangeEvent = useCallback((event: HelpNameChangeEvent) => useUiEvent<HelpNameChangeEvent>(HelpNameChangeEvent.INIT, event =>
{ {
setLayout(INIT); setLayout(INIT);
setIsVisible(true); setIsVisible(true);
}, []); });
useUiEvent(HelpNameChangeEvent.INIT, onHelpNameChangeEvent);
const onAction = useCallback((action: string, value?: string) => const onAction = useCallback((action: string, value?: string) =>
{ {

View File

@ -6,12 +6,12 @@ import { useInventoryBadges, useInventoryUnseenTracker } from '../../../../hooks
export const InventoryBadgeItemView: FC<PropsWithChildren<{ badgeCode: string }>> = props => export const InventoryBadgeItemView: FC<PropsWithChildren<{ badgeCode: string }>> = props =>
{ {
const { badgeCode = null, children = null, ...rest } = 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 { isUnseen = null } = useInventoryUnseenTracker();
const unseen = isUnseen(UnseenItemCategory.BADGE, getBadgeId(badgeCode)); const unseen = isUnseen(UnseenItemCategory.BADGE, getBadgeId(badgeCode));
return ( 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 } /> <LayoutBadgeImageView badgeCode={ badgeCode } />
{ children } { children }
</LayoutGridItem> </LayoutGridItem>

View File

@ -26,13 +26,16 @@ export const InventoryBotItemView: FC<PropsWithChildren<{ botItem: IBotItem }>>
case MouseEventType.ROLL_OUT: case MouseEventType.ROLL_OUT:
if(!isMouseDown || (selectedBot !== botItem)) return; if(!isMouseDown || (selectedBot !== botItem)) return;
attemptBotPlacement(botItem);
return;
case 'dblclick':
attemptBotPlacement(botItem); attemptBotPlacement(botItem);
return; return;
} }
} }
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 } /> <LayoutAvatarImageView figure={ botItem.botData.figure } direction={ 3 } headOnly={ true } />
{ children } { children }
</LayoutGridItem> </LayoutGridItem>

View File

@ -24,6 +24,9 @@ export const InventoryFurnitureItemView: FC<{ groupItem: GroupItem }> = props =>
case MouseEventType.ROLL_OUT: case MouseEventType.ROLL_OUT:
if(!isMouseDown || !(groupItem === selectedItem)) return; if(!isMouseDown || !(groupItem === selectedItem)) return;
attemptItemPlacement(groupItem);
return;
case 'dblclick':
attemptItemPlacement(groupItem); attemptItemPlacement(groupItem);
return; return;
} }
@ -31,5 +34,5 @@ export const InventoryFurnitureItemView: FC<{ groupItem: GroupItem }> = props =>
const count = groupItem.getUnlockedCount(); 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 } />;
} }

View File

@ -21,6 +21,7 @@ export const InventoryTradeView: FC<InventoryTradeViewProps> = props =>
const [ otherGroupItem, setOtherGroupItem ] = useState<GroupItem>(null); const [ otherGroupItem, setOtherGroupItem ] = useState<GroupItem>(null);
const [ filteredGroupItems, setFilteredGroupItems ] = useState<GroupItem[]>(null); const [ filteredGroupItems, setFilteredGroupItems ] = useState<GroupItem[]>(null);
const [ countdownTick, setCountdownTick ] = useState(3); 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 { ownUser = null, otherUser = null, groupItems = [], tradeState = TradeState.TRADING_STATE_READY, progressTrade = null, removeItem = null, setTradeState = null } = useInventoryTrade();
const { simpleAlert = null } = useNotification(); const { simpleAlert = null } = useNotification();
@ -118,6 +119,29 @@ export const InventoryTradeView: FC<InventoryTradeViewProps> = props =>
return <FontAwesomeIcon icon={ iconName } className={ 'text-' + textColor } /> 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(() => useEffect(() =>
{ {
if(tradeState !== TradeState.TRADING_STATE_COUNTDOWN) return; if(tradeState !== TradeState.TRADING_STATE_COUNTDOWN) return;
@ -159,18 +183,29 @@ export const InventoryTradeView: FC<InventoryTradeViewProps> = props =>
const count = item.getUnlockedCount(); const count = item.getUnlockedCount();
return ( 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)) && { ((count > 0) && (groupItem === item)) &&
<Button position="absolute" variant="success" className="trade-button bottom-1 end-1" onClick={ event => attemptItemOffer(1) }> <Button position="absolute" variant="success" className="trade-button bottom-1 end-1" onClick={ event => attemptItemOffer(1) }>
<FontAwesomeIcon icon="chevron-right" /> <FontAwesomeIcon icon="chevron-right" />
</Button> } </Button>
}
</LayoutGridItem> </LayoutGridItem>
); );
}) } }) }
</AutoGrid> </AutoGrid>
<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"> <Base fullWidth className="badge bg-muted">
{ groupItem ? groupItem.name : LocalizeText('catalog_selectproduct') } { groupItem ? groupItem.name : LocalizeText('catalog_selectproduct') }
</Base> </Base>
</Column>
</Flex> </Flex>
</Column> </Column>
<Column size={ 8 } overflow="hidden"> <Column size={ 8 } overflow="hidden">
@ -188,7 +223,7 @@ export const InventoryTradeView: FC<InventoryTradeViewProps> = props =>
if(!item) return <LayoutGridItem key={ i } />; if(!item) return <LayoutGridItem key={ i } />;
return ( 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) && { (ownGroupItem === item) &&
<Button position="absolute" variant="danger" className="trade-button bottom-1 start-1" onClick={ event => removeItem(item) }> <Button position="absolute" variant="danger" className="trade-button bottom-1 start-1" onClick={ event => removeItem(item) }>
<FontAwesomeIcon icon="chevron-left" /> <FontAwesomeIcon icon="chevron-left" />

View File

@ -26,13 +26,16 @@ export const InventoryPetItemView: FC<PropsWithChildren<{ petItem: IPetItem }>>
case MouseEventType.ROLL_OUT: case MouseEventType.ROLL_OUT:
if(!isMouseDown || !(petItem === selectedPet)) return; if(!isMouseDown || !(petItem === selectedPet)) return;
attemptPetPlacement(petItem);
return;
case 'dblclick':
attemptPetPlacement(petItem); attemptPetPlacement(petItem);
return; return;
} }
} }
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 } /> <LayoutPetImageView figure={ petItem.petData.figureData.figuredata } direction={ 3 } headOnly={ true } />
{ children } { children }
</LayoutGridItem> </LayoutGridItem>

View File

@ -1,9 +1,9 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { PetRespectComposer, PetType } from '@nitrots/nitro-renderer'; import { PetRespectComposer, PetType } from '@nitrots/nitro-renderer';
import { FC, useEffect, useState } from 'react'; import { FC, useEffect, useState } from 'react';
import { AvatarInfoPet, ConvertSeconds, CreateLinkEvent, GetConfiguration, GetSessionDataManager, LocalizeText, SendMessageComposer } from '../../../../../api'; import { AvatarInfoPet, CreateLinkEvent, GetConfiguration, LocalizeText, SendMessageComposer, ConvertSeconds } from '../../../../../api';
import { Base, Button, Column, Flex, LayoutCounterTimeView, LayoutPetImageView, LayoutRarityLevelView, Text, UserProfileIconView } from '../../../../../common'; import { Base, Button, Column, Flex, LayoutPetImageView, LayoutRarityLevelView, Text, UserProfileIconView, LayoutCounterTimeView } from '../../../../../common';
import { usePets, useRoom } from '../../../../../hooks'; import { useRoom, useSessionInfo } from '../../../../../hooks';
interface InfoStandWidgetPetViewProps interface InfoStandWidgetPetViewProps
{ {
@ -14,21 +14,32 @@ interface InfoStandWidgetPetViewProps
export const InfoStandWidgetPetView: FC<InfoStandWidgetPetViewProps> = props => export const InfoStandWidgetPetView: FC<InfoStandWidgetPetViewProps> = props =>
{ {
const { avatarInfo = null, onClose = null } = props; const { avatarInfo = null, onClose = null } = props;
const { roomSession = null } = useRoom();
const { petRespect, changePetRespect } = usePets();
const [ remainingGrowTime, setRemainingGrowTime ] = useState(0); const [ remainingGrowTime, setRemainingGrowTime ] = useState(0);
const [ remainingTimeToLive, setRemainingTimeToLive ] = useState(0); const [ remainingTimeToLive, setRemainingTimeToLive ] = useState(0);
const { roomSession = null } = useRoom();
if(!avatarInfo) return null; const { petRespectRemaining = 0, respectPet = null } = useSessionInfo();
useEffect(() => useEffect(() =>
{ {
changePetRespect(avatarInfo.respectsPetLeft);
setRemainingGrowTime(avatarInfo.remainingGrowTime); setRemainingGrowTime(avatarInfo.remainingGrowTime);
setRemainingTimeToLive(avatarInfo.remainingTimeToLive); setRemainingTimeToLive(avatarInfo.remainingTimeToLive);
}, [ avatarInfo ]); }, [ 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) => const processButtonAction = (action: string) =>
{ {
let hideMenu = true; let hideMenu = true;
@ -38,17 +49,9 @@ export const InfoStandWidgetPetView: FC<InfoStandWidgetPetViewProps> = props =>
switch (action) switch (action)
{ {
case 'respect': case 'respect':
let newRespectsLeftChange = 0; respectPet(avatarInfo.id);
changePetRespect(prevValue => if((petRespectRemaining - 1) >= 1) hideMenu = false;
{
newRespectsLeftChange = (prevValue - 1);
return newRespectsLeftChange;
});
GetSessionDataManager().givePetRespect(avatarInfo.id);
if(newRespectsLeftChange > 0) hideMenu = false;
break; break;
case 'buyfood': case 'buyfood':
CreateLinkEvent('catalog/open/' + GetConfiguration('catalog.links')['pets.buy_saddle']); CreateLinkEvent('catalog/open/' + GetConfiguration('catalog.links')['pets.buy_saddle']);
@ -70,46 +73,6 @@ export const InfoStandWidgetPetView: FC<InfoStandWidgetPetViewProps> = props =>
if(hideMenu) onClose(); if(hideMenu) onClose();
} }
useEffect(() =>
{
changePetRespect(avatarInfo.respectsPetLeft);
setRemainingGrowTime(avatarInfo.remainingGrowTime);
setRemainingTimeToLive(avatarInfo.remainingTimeToLive);
}, [ avatarInfo ]);
useEffect(() =>
{
if (avatarInfo.petType === PetType.MONSTERPLANT && !avatarInfo.dead)
{
const interval = setInterval(() =>
{
let newRemaingGrowTime = 0;
let newRemaingLiveTime = 0;
setRemainingGrowTime(prevValue =>
{
newRemaingGrowTime = (prevValue - 1);
return newRemaingGrowTime;
});
setRemainingTimeToLive(prevValue =>
{
newRemaingLiveTime = (prevValue - 1);
return newRemaingLiveTime;
});
}, 1000);
return () => clearInterval(interval);
}
}, [ avatarInfo ]);
const InfoStandNormalPet = () =>
{
return ( return (
<Column gap={ 1 } alignItems="end"> <Column gap={ 1 } alignItems="end">
<Column className="nitro-infostand rounded"> <Column className="nitro-infostand rounded">
@ -122,6 +85,50 @@ export const InfoStandWidgetPetView: FC<InfoStandWidgetPetViewProps> = props =>
<Text variant="white" small wrap>{ LocalizeText(`pet.breed.${ avatarInfo.petType }.${ avatarInfo.petBreed }`) }</Text> <Text variant="white" small wrap>{ LocalizeText(`pet.breed.${ avatarInfo.petType }.${ avatarInfo.petBreed }`) }</Text>
<hr className="m-0" /> <hr className="m-0" />
</Column> </Column>
{ (avatarInfo.petType === PetType.MONSTERPLANT) &&
<>
<Column gap={ 1 }>
<Flex gap={ 1 }>
<Column fullWidth overflow="hidden" className="body-image-plant pet p-1">
<LayoutPetImageView figure={ avatarInfo.petFigure } posture={ avatarInfo.posture } direction={ 4 } />
</Column>
{ !avatarInfo.dead &&
<Column grow gap={ 1 }>
<Text variant="white" center small wrap>{ LocalizeText('pet.level', [ 'level', 'maxlevel' ], [ avatarInfo.level.toString(), avatarInfo.maximumLevel.toString() ]) }</Text>
</Column> }
</Flex>
<hr className="m-0" />
</Column>
<Column gap={ 1 }>
<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>
<br /><br />
<br /><br />
{ remainingGrowTime != 0 && remainingGrowTime > 0 &&
<Column alignItems="center" gap={ 1 }>
<Text variant="white" small truncate>{ LocalizeText('infostand.pet.text.growth') }</Text> <br />
<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> }
<br /><br />
<br /><br />
<Column alignItems="center" gap={ 1 }>
<Text variant="white" small truncate>{ LocalizeText('Nivel de rareza:') }</Text>
<LayoutRarityLevelView className="top-2 end-2" level={ avatarInfo.rarityLevel } />
</Column>
<br /><br />
<Text variant="white" small wrap>{ LocalizeText('pet.age', [ 'age' ], [ avatarInfo.age.toString() ]) }</Text>
<hr className="m-0" />
</Column>
</> }
{ (avatarInfo.petType !== PetType.MONSTERPLANT) &&
<>
<Column gap={ 1 }> <Column gap={ 1 }>
<Flex gap={ 1 }> <Flex gap={ 1 }>
<Column fullWidth overflow="hidden" className="body-image pet p-1"> <Column fullWidth overflow="hidden" className="body-image pet p-1">
@ -161,9 +168,12 @@ export const InfoStandWidgetPetView: FC<InfoStandWidgetPetViewProps> = props =>
<hr className="m-0" /> <hr className="m-0" />
</Column> </Column>
<Column gap={ 1 }> <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> <Text variant="white" small wrap>{ LocalizeText('pet.age', [ 'age' ], [ avatarInfo.age.toString() ]) }</Text>
<hr className="m-0" /> <hr className="m-0" />
</Column> </Column>
</> }
<Column gap={ 1 }> <Column gap={ 1 }>
<Flex alignItems="center" gap={ 1 }> <Flex alignItems="center" gap={ 1 }>
<UserProfileIconView userId={ avatarInfo.ownerId } /> <UserProfileIconView userId={ avatarInfo.ownerId } />
@ -175,124 +185,31 @@ export const InfoStandWidgetPetView: FC<InfoStandWidgetPetViewProps> = props =>
</Column> </Column>
</Column> </Column>
<Flex gap={ 1 } justifyContent="end"> <Flex gap={ 1 } justifyContent="end">
{ (avatarInfo.petType !== PetType.MONSTERPLANT) &&
<Button variant="dark" onClick={ event => processButtonAction('buyfood') }> <Button variant="dark" onClick={ event => processButtonAction('buyfood') }>
{ LocalizeText('infostand.button.buyfood') } { LocalizeText('infostand.button.buyfood') }
</Button> </Button> }
{ avatarInfo.isOwner && { avatarInfo.isOwner && (avatarInfo.petType !== PetType.MONSTERPLANT) &&
<Button variant="dark" onClick={ event => processButtonAction('train') }> <Button variant="dark" onClick={ event => processButtonAction('train') }>
{ LocalizeText('infostand.button.train') } { LocalizeText('infostand.button.train') }
</Button> </Button> }
} { !avatarInfo.dead && ((avatarInfo.energy / avatarInfo.maximumEnergy) < 0.98) && (avatarInfo.petType === PetType.MONSTERPLANT) &&
{ avatarInfo.isOwner &&
<Button variant="dark" onClick={ event => processButtonAction('pick_up') }>
{ LocalizeText('inventory.pets.pickup') }
</Button>
}
{ (petRespect > 0) &&
<Button variant="dark" onClick={ event => processButtonAction('respect') }>
{ LocalizeText('infostand.button.petrespect', [ 'count' ], [ petRespect.toString() ]) }
</Button>
}
</Flex>
</Column>
);
}
const InfoStandMonsterplantPet = () =>
{
return (
<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>
<Column gap={ 1 }>
<Flex gap={ 1 }>
<Column fullWidth overflow="hidden" className="body-image-plant pet p-1">
<LayoutPetImageView figure={ avatarInfo.petFigure } posture={ avatarInfo.posture } direction={ 4 } />
</Column>
{ !avatarInfo.dead &&
<Column grow gap={ 1 }>
<Text variant="white" center small wrap>{ LocalizeText('pet.level', [ 'level', 'maxlevel' ], [ avatarInfo.level.toString(), avatarInfo.maximumLevel.toString() ]) }</Text>
</Column>
}
</Flex>
<hr className="m-0" />
</Column>
<br /><br />
<Column gap={ 1 }>
<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>
<br /><br />
<br /><br />
{ remainingGrowTime != 0 && remainingGrowTime > 0 &&
<Column alignItems="center" gap={ 1 }>
<Text variant="white" small truncate>{ LocalizeText('infostand.pet.text.growth') }</Text> <br />
<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>
}
<br /><br />
<br /><br />
<Column alignItems="center" gap={ 1 }>
<Text variant="white" small truncate>{ LocalizeText('Nivel de rareza:') }</Text>
<LayoutRarityLevelView className="top-2 end-2" level={ avatarInfo.rarityLevel } />
</Column>
<br /><br />
<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.dead && ((avatarInfo.energy / avatarInfo.maximumEnergy) < 0.98) &&
<Button variant="dark" onClick={ event => processButtonAction('treat') }> <Button variant="dark" onClick={ event => processButtonAction('treat') }>
{ LocalizeText('infostand.button.pettreat') } { LocalizeText('infostand.button.pettreat') }
</Button> </Button> }
} { roomSession?.isRoomOwner && (avatarInfo.petType === PetType.MONSTERPLANT) &&
{ roomSession?.isRoomOwner &&
<Button variant="dark" onClick={ event => processButtonAction('compost') }> <Button variant="dark" onClick={ event => processButtonAction('compost') }>
{ LocalizeText('infostand.button.compost') } { LocalizeText('infostand.button.compost') }
</Button> </Button> }
}
{ avatarInfo.isOwner && { avatarInfo.isOwner &&
<Button variant="dark" onClick={ event => processButtonAction('pick_up') }> <Button variant="dark" onClick={ event => processButtonAction('pick_up') }>
{ LocalizeText('inventory.pets.pickup') } { LocalizeText('inventory.pets.pickup') }
</Button> </Button> }
} { (petRespectRemaining > 0) && (avatarInfo.petType !== PetType.MONSTERPLANT) &&
<Button variant="dark" onClick={ event => processButtonAction('respect') }>
{ LocalizeText('infostand.button.petrespect', [ 'count' ], [ petRespectRemaining.toString() ]) }
</Button> }
</Flex> </Flex>
</Column> </Column>
); );
} }
return (
<>
{ avatarInfo.petType !== PetType.MONSTERPLANT &&
<InfoStandNormalPet />
}
{ avatarInfo.petType === PetType.MONSTERPLANT &&
<InfoStandMonsterplantPet />
}
</>
);
}

View File

@ -3,7 +3,7 @@ import { RoomControllerLevel, RoomObjectCategory, RoomObjectVariable, RoomUnitGi
import { FC, useEffect, useMemo, useState } from 'react'; import { FC, useEffect, useMemo, useState } from 'react';
import { AvatarInfoUser, CreateLinkEvent, DispatchUiEvent, GetOwnRoomObject, GetSessionDataManager, GetUserProfile, LocalizeText, MessengerFriend, ReportType, RoomWidgetUpdateChatInputContentEvent, SendMessageComposer } from '../../../../../api'; import { AvatarInfoUser, CreateLinkEvent, DispatchUiEvent, GetOwnRoomObject, GetSessionDataManager, GetUserProfile, LocalizeText, MessengerFriend, ReportType, RoomWidgetUpdateChatInputContentEvent, SendMessageComposer } from '../../../../../api';
import { Base, Flex } from '../../../../../common'; 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 { ContextMenuHeaderView } from '../../context-menu/ContextMenuHeaderView';
import { ContextMenuListItemView } from '../../context-menu/ContextMenuListItemView'; import { ContextMenuListItemView } from '../../context-menu/ContextMenuListItemView';
import { ContextMenuView } from '../../context-menu/ContextMenuView'; import { ContextMenuView } from '../../context-menu/ContextMenuView';
@ -26,10 +26,10 @@ export const AvatarInfoWidgetAvatarView: FC<AvatarInfoWidgetAvatarViewProps> = p
{ {
const { avatarInfo = null, onClose = null } = props; const { avatarInfo = null, onClose = null } = props;
const [ mode, setMode ] = useState(MODE_NORMAL); const [ mode, setMode ] = useState(MODE_NORMAL);
const [ respectsLeft, setRespectsLeft ] = useState(0);
const { canRequestFriend = null } = useFriends(); const { canRequestFriend = null } = useFriends();
const { report = null } = useHelp(); const { report = null } = useHelp();
const { roomSession = null } = useRoom(); const { roomSession = null } = useRoom();
const { userRespectRemaining = 0, respectUser = null } = useSessionInfo();
const isShowGiveRights = useMemo(() => const isShowGiveRights = useMemo(() =>
{ {
@ -113,13 +113,9 @@ export const AvatarInfoWidgetAvatarView: FC<AvatarInfoWidgetAvatarViewProps> = p
setMode(MODE_RELATIONSHIP); setMode(MODE_RELATIONSHIP);
break; break;
case 'respect': { case 'respect': {
let newRespectsLeft = (respectsLeft - 1); respectUser(avatarInfo.webID);
setRespectsLeft(newRespectsLeft); if((userRespectRemaining - 1) >= 1) hideMenu = false;
GetSessionDataManager().giveRespect(avatarInfo.webID);
if(newRespectsLeft > 0) hideMenu = false;
break; break;
} }
case 'ignore': case 'ignore':
@ -203,7 +199,6 @@ export const AvatarInfoWidgetAvatarView: FC<AvatarInfoWidgetAvatarViewProps> = p
useEffect(() => useEffect(() =>
{ {
setMode(MODE_NORMAL); setMode(MODE_NORMAL);
setRespectsLeft(avatarInfo.respectLeft);
}, [ avatarInfo ]); }, [ avatarInfo ]);
return ( return (
@ -223,9 +218,9 @@ export const AvatarInfoWidgetAvatarView: FC<AvatarInfoWidgetAvatarViewProps> = p
<ContextMenuListItemView onClick={ event => processAction('whisper') }> <ContextMenuListItemView onClick={ event => processAction('whisper') }>
{ LocalizeText('infostand.button.whisper') } { LocalizeText('infostand.button.whisper') }
</ContextMenuListItemView> </ContextMenuListItemView>
{ (respectsLeft > 0) && { (userRespectRemaining > 0) &&
<ContextMenuListItemView onClick={ event => processAction('respect') }> <ContextMenuListItemView onClick={ event => processAction('respect') }>
{ LocalizeText('infostand.button.respect', [ 'count' ], [ respectsLeft.toString() ]) } { LocalizeText('infostand.button.respect', [ 'count' ], [ userRespectRemaining.toString() ]) }
</ContextMenuListItemView> } </ContextMenuListItemView> }
{ !canRequestFriend(avatarInfo.webID) && { !canRequestFriend(avatarInfo.webID) &&
<ContextMenuListItemView onClick={ event => processAction('relationship') }> <ContextMenuListItemView onClick={ event => processAction('relationship') }>

View File

@ -1,7 +1,7 @@
import { PetRespectComposer, PetType, RoomObjectCategory, RoomObjectType, RoomObjectVariable, RoomUnitGiveHandItemPetComposer } from '@nitrots/nitro-renderer'; import { PetRespectComposer, PetType, RoomObjectCategory, RoomObjectType, RoomObjectVariable, RoomUnitGiveHandItemPetComposer } from '@nitrots/nitro-renderer';
import { FC, useEffect, useMemo, useState } from 'react'; import { FC, useEffect, useMemo, useState } from 'react';
import { AvatarInfoPet, CreateLinkEvent, GetConfiguration, GetOwnRoomObject, GetSessionDataManager, LocalizeText, SendMessageComposer } from '../../../../../api'; import { AvatarInfoPet, CreateLinkEvent, GetConfiguration, GetOwnRoomObject, LocalizeText, SendMessageComposer } from '../../../../../api';
import { useRoom } from '../../../../../hooks'; import { useRoom, useSessionInfo } from '../../../../../hooks';
import { ContextMenuHeaderView } from '../../context-menu/ContextMenuHeaderView'; import { ContextMenuHeaderView } from '../../context-menu/ContextMenuHeaderView';
import { ContextMenuListItemView } from '../../context-menu/ContextMenuListItemView'; import { ContextMenuListItemView } from '../../context-menu/ContextMenuListItemView';
import { ContextMenuView } from '../../context-menu/ContextMenuView'; import { ContextMenuView } from '../../context-menu/ContextMenuView';
@ -21,8 +21,8 @@ export const AvatarInfoWidgetOwnPetView: FC<AvatarInfoWidgetOwnPetViewProps> = p
{ {
const { avatarInfo = null, onClose = null } = props; const { avatarInfo = null, onClose = null } = props;
const [ mode, setMode ] = useState(MODE_NORMAL); const [ mode, setMode ] = useState(MODE_NORMAL);
const [ respectsLeft, setRespectsLeft ] = useState(0);
const { roomSession = null } = useRoom(); const { roomSession = null } = useRoom();
const { petRespectRemaining = 0, respectPet = null } = useSessionInfo();
const canGiveHandItem = useMemo(() => const canGiveHandItem = useMemo(() =>
{ {
@ -49,18 +49,9 @@ export const AvatarInfoWidgetOwnPetView: FC<AvatarInfoWidgetOwnPetViewProps> = p
switch(name) switch(name)
{ {
case 'respect': case 'respect':
let newRespectsLeft = 0; respectPet(avatarInfo.id);
setRespectsLeft(prevValue => if((petRespectRemaining - 1) >= 1) hideMenu = false;
{
newRespectsLeft = (prevValue - 1);
return newRespectsLeft;
});
GetSessionDataManager().givePetRespect(avatarInfo.id);
if(newRespectsLeft > 0) hideMenu = false;
break; break;
case 'treat': case 'treat':
SendMessageComposer(new PetRespectComposer(avatarInfo.id)); SendMessageComposer(new PetRespectComposer(avatarInfo.id));
@ -131,8 +122,6 @@ export const AvatarInfoWidgetOwnPetView: FC<AvatarInfoWidgetOwnPetViewProps> = p
return MODE_NORMAL; return MODE_NORMAL;
}); });
setRespectsLeft(avatarInfo.respectsPetLeft);
}, [ avatarInfo ]); }, [ avatarInfo ]);
return ( return (
@ -142,9 +131,9 @@ export const AvatarInfoWidgetOwnPetView: FC<AvatarInfoWidgetOwnPetViewProps> = p
</ContextMenuHeaderView> </ContextMenuHeaderView>
{ (mode === MODE_NORMAL) && { (mode === MODE_NORMAL) &&
<> <>
{ (respectsLeft > 0) && { (petRespectRemaining > 0) &&
<ContextMenuListItemView onClick={ event => processAction('respect') }> <ContextMenuListItemView onClick={ event => processAction('respect') }>
{ LocalizeText('infostand.button.petrespect', [ 'count' ], [ respectsLeft.toString() ]) } { LocalizeText('infostand.button.petrespect', [ 'count' ], [ petRespectRemaining.toString() ]) }
</ContextMenuListItemView> } </ContextMenuListItemView> }
<ContextMenuListItemView onClick={ event => processAction('train') }> <ContextMenuListItemView onClick={ event => processAction('train') }>
{ LocalizeText('infostand.button.train') } { LocalizeText('infostand.button.train') }
@ -170,9 +159,9 @@ export const AvatarInfoWidgetOwnPetView: FC<AvatarInfoWidgetOwnPetViewProps> = p
<input type="checkbox" checked={ !!avatarInfo.publiclyRideable } readOnly={ true } /> <input type="checkbox" checked={ !!avatarInfo.publiclyRideable } readOnly={ true } />
{ LocalizeText('infostand.button.toggle_riding_permission') } { LocalizeText('infostand.button.toggle_riding_permission') }
</ContextMenuListItemView> </ContextMenuListItemView>
{ (respectsLeft > 0) && { (petRespectRemaining > 0) &&
<ContextMenuListItemView onClick={ event => processAction('respect') }> <ContextMenuListItemView onClick={ event => processAction('respect') }>
{ LocalizeText('infostand.button.petrespect', [ 'count' ], [ respectsLeft.toString() ]) } { LocalizeText('infostand.button.petrespect', [ 'count' ], [ petRespectRemaining.toString() ]) }
</ContextMenuListItemView> } </ContextMenuListItemView> }
<ContextMenuListItemView onClick={ event => processAction('train') }> <ContextMenuListItemView onClick={ event => processAction('train') }>
{ LocalizeText('infostand.button.train') } { LocalizeText('infostand.button.train') }
@ -189,9 +178,9 @@ export const AvatarInfoWidgetOwnPetView: FC<AvatarInfoWidgetOwnPetViewProps> = p
<ContextMenuListItemView onClick={ event => processAction('dismount') }> <ContextMenuListItemView onClick={ event => processAction('dismount') }>
{ LocalizeText('infostand.button.dismount') } { LocalizeText('infostand.button.dismount') }
</ContextMenuListItemView> </ContextMenuListItemView>
{ (respectsLeft > 0) && { (petRespectRemaining > 0) &&
<ContextMenuListItemView onClick={ event => processAction('respect') }> <ContextMenuListItemView onClick={ event => processAction('respect') }>
{ LocalizeText('infostand.button.petrespect', [ 'count' ], [ respectsLeft.toString() ]) } { LocalizeText('infostand.button.petrespect', [ 'count' ], [ petRespectRemaining.toString() ]) }
</ContextMenuListItemView> } </ContextMenuListItemView> }
</> } </> }
{ (mode === MODE_MONSTER_PLANT) && { (mode === MODE_MONSTER_PLANT) &&
@ -209,7 +198,7 @@ export const AvatarInfoWidgetOwnPetView: FC<AvatarInfoWidgetOwnPetViewProps> = p
</ContextMenuListItemView> } </ContextMenuListItemView> }
{ !avatarInfo.dead && ((avatarInfo.energy / avatarInfo.maximumEnergy) < 0.98) && { !avatarInfo.dead && ((avatarInfo.energy / avatarInfo.maximumEnergy) < 0.98) &&
<ContextMenuListItemView onClick={ event => processAction('treat') }> <ContextMenuListItemView onClick={ event => processAction('treat') }>
{ LocalizeText('infostand.button.treat') } { LocalizeText('infostand.button.pettreat') }
</ContextMenuListItemView> } </ContextMenuListItemView> }
{ !avatarInfo.dead && (avatarInfo.level === avatarInfo.maximumLevel) && avatarInfo.breedable && { !avatarInfo.dead && (avatarInfo.level === avatarInfo.maximumLevel) && avatarInfo.breedable &&
<> <>

View File

@ -1,7 +1,7 @@
import { PetRespectComposer, PetType, RoomControllerLevel, RoomObjectCategory, RoomObjectType, RoomObjectVariable, RoomUnitGiveHandItemPetComposer } from '@nitrots/nitro-renderer'; import { PetRespectComposer, PetType, RoomControllerLevel, RoomObjectCategory, RoomObjectType, RoomObjectVariable, RoomUnitGiveHandItemPetComposer } from '@nitrots/nitro-renderer';
import { FC, useEffect, useMemo, useState } from 'react'; import { FC, useEffect, useMemo, useState } from 'react';
import { AvatarInfoPet, GetOwnRoomObject, GetSessionDataManager, LocalizeText, SendMessageComposer } from '../../../../../api'; import { AvatarInfoPet, GetOwnRoomObject, GetSessionDataManager, LocalizeText, SendMessageComposer } from '../../../../../api';
import { useRoom } from '../../../../../hooks'; import { useRoom, useSessionInfo } from '../../../../../hooks';
import { ContextMenuHeaderView } from '../../context-menu/ContextMenuHeaderView'; import { ContextMenuHeaderView } from '../../context-menu/ContextMenuHeaderView';
import { ContextMenuListItemView } from '../../context-menu/ContextMenuListItemView'; import { ContextMenuListItemView } from '../../context-menu/ContextMenuListItemView';
import { ContextMenuView } from '../../context-menu/ContextMenuView'; import { ContextMenuView } from '../../context-menu/ContextMenuView';
@ -21,8 +21,8 @@ export const AvatarInfoWidgetPetView: FC<AvatarInfoWidgetPetViewProps> = props =
{ {
const { avatarInfo = null, onClose = null } = props; const { avatarInfo = null, onClose = null } = props;
const [ mode, setMode ] = useState(MODE_NORMAL); const [ mode, setMode ] = useState(MODE_NORMAL);
const [ respectsLeft, setRespectsLeft ] = useState(0);
const { roomSession = null } = useRoom(); const { roomSession = null } = useRoom();
const { petRespectRemaining = 0, respectPet = null } = useSessionInfo();
const canPickUp = useMemo(() => const canPickUp = useMemo(() =>
{ {
@ -54,18 +54,9 @@ export const AvatarInfoWidgetPetView: FC<AvatarInfoWidgetPetViewProps> = props =
switch(name) switch(name)
{ {
case 'respect': case 'respect':
let newRespectsLeft = 0; respectPet(avatarInfo.id);
setRespectsLeft(prevValue => if((petRespectRemaining - 1) >= 1) hideMenu = false;
{
newRespectsLeft = (prevValue - 1);
return newRespectsLeft;
});
GetSessionDataManager().givePetRespect(avatarInfo.id);
if(newRespectsLeft > 0) hideMenu = false;
break; break;
case 'treat': case 'treat':
SendMessageComposer(new PetRespectComposer(avatarInfo.id)); SendMessageComposer(new PetRespectComposer(avatarInfo.id));
@ -98,8 +89,6 @@ export const AvatarInfoWidgetPetView: FC<AvatarInfoWidgetPetViewProps> = props =
return MODE_NORMAL; return MODE_NORMAL;
}); });
setRespectsLeft(avatarInfo.respectsPetLeft);
}, [ avatarInfo ]); }, [ avatarInfo ]);
return ( return (
@ -107,9 +96,9 @@ export const AvatarInfoWidgetPetView: FC<AvatarInfoWidgetPetViewProps> = props =
<ContextMenuHeaderView> <ContextMenuHeaderView>
{ avatarInfo.name } { avatarInfo.name }
</ContextMenuHeaderView> </ContextMenuHeaderView>
{ (mode === MODE_NORMAL) && (respectsLeft > 0) && { (mode === MODE_NORMAL) && (petRespectRemaining > 0) &&
<ContextMenuListItemView onClick={ event => processAction('respect') }> <ContextMenuListItemView onClick={ event => processAction('respect') }>
{ LocalizeText('infostand.button.petrespect', [ 'count' ], [ respectsLeft.toString() ]) } { LocalizeText('infostand.button.petrespect', [ 'count' ], [ petRespectRemaining.toString() ]) }
</ContextMenuListItemView> } </ContextMenuListItemView> }
{ (mode === MODE_SADDLED_UP) && { (mode === MODE_SADDLED_UP) &&
<> <>
@ -117,9 +106,9 @@ export const AvatarInfoWidgetPetView: FC<AvatarInfoWidgetPetViewProps> = props =
<ContextMenuListItemView onClick={ event => processAction('mount') }> <ContextMenuListItemView onClick={ event => processAction('mount') }>
{ LocalizeText('infostand.button.mount') } { LocalizeText('infostand.button.mount') }
</ContextMenuListItemView> } </ContextMenuListItemView> }
{ (respectsLeft > 0) && { (petRespectRemaining > 0) &&
<ContextMenuListItemView onClick={ event => processAction('respect') }> <ContextMenuListItemView onClick={ event => processAction('respect') }>
{ LocalizeText('infostand.button.petrespect', [ 'count' ], [ respectsLeft.toString() ]) } { LocalizeText('infostand.button.petrespect', [ 'count' ], [ petRespectRemaining.toString() ]) }
</ContextMenuListItemView> } </ContextMenuListItemView> }
</> } </> }
{ (mode === MODE_RIDING) && { (mode === MODE_RIDING) &&
@ -127,14 +116,14 @@ export const AvatarInfoWidgetPetView: FC<AvatarInfoWidgetPetViewProps> = props =
<ContextMenuListItemView onClick={ event => processAction('dismount') }> <ContextMenuListItemView onClick={ event => processAction('dismount') }>
{ LocalizeText('infostand.button.dismount') } { LocalizeText('infostand.button.dismount') }
</ContextMenuListItemView> </ContextMenuListItemView>
{ (respectsLeft > 0) && { (petRespectRemaining > 0) &&
<ContextMenuListItemView onClick={ event => processAction('respect') }> <ContextMenuListItemView onClick={ event => processAction('respect') }>
{ LocalizeText('infostand.button.petrespect', [ 'count' ], [ respectsLeft.toString() ]) } { LocalizeText('infostand.button.petrespect', [ 'count' ], [ petRespectRemaining.toString() ]) }
</ContextMenuListItemView> } </ContextMenuListItemView> }
</> } </> }
{ (mode === MODE_MONSTER_PLANT) && !avatarInfo.dead && ((avatarInfo.energy / avatarInfo.maximumEnergy) < 0.98) && { (mode === MODE_MONSTER_PLANT) && !avatarInfo.dead && ((avatarInfo.energy / avatarInfo.maximumEnergy) < 0.98) &&
<ContextMenuListItemView onClick={ event => processAction('treat') }> <ContextMenuListItemView onClick={ event => processAction('treat') }>
{ LocalizeText('infostand.button.treat') } { LocalizeText('infostand.button.pettreat') }
</ContextMenuListItemView> } </ContextMenuListItemView> }
{ canPickUp && { canPickUp &&
<ContextMenuListItemView onClick={ event => processAction('pick_up') }> <ContextMenuListItemView onClick={ event => processAction('pick_up') }>

View File

@ -88,10 +88,17 @@ export const ChatInputView: FC<{}> = props =>
setIsIdle(false); setIsIdle(false);
if(text.length <= maxChatLength) if(text.length <= maxChatLength)
{
if(/%CC%/g.test(encodeURIComponent(text)))
{
setChatValue('');
}
else
{ {
setChatValue(''); setChatValue('');
sendChat(text, chatType, recipientName, chatStyleId); sendChat(text, chatType, recipientName, chatStyleId);
} }
}
setChatValue(append); setChatValue(append);
}, [ chatModeIdWhisper, chatModeIdShout, chatModeIdSpeak, maxChatLength, chatStyleId, setIsTyping, setIsIdle, sendChat ]); }, [ chatModeIdWhisper, chatModeIdShout, chatModeIdSpeak, maxChatLength, chatStyleId, setIsTyping, setIsIdle, sendChat ]);

View File

@ -1,5 +1,5 @@
import { FurnitureStackHeightComposer } from '@nitrots/nitro-renderer'; import { FurnitureStackHeightComposer } from '@nitrots/nitro-renderer';
import { FC } from 'react'; import { FC, useEffect, useState } from 'react';
import ReactSlider from 'react-slider'; import ReactSlider from 'react-slider';
import { LocalizeText, SendMessageComposer } from '../../../../api'; import { LocalizeText, SendMessageComposer } from '../../../../api';
import { Button, Column, Flex, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../../common'; import { Button, Column, Flex, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../../common';
@ -8,6 +8,23 @@ import { useFurnitureStackHeightWidget } from '../../../../hooks';
export const FurnitureStackHeightView: FC<{}> = props => export const FurnitureStackHeightView: FC<{}> = props =>
{ {
const { objectId = -1, height = 0, maxHeight = 40, onClose = null, updateHeight = null } = useFurnitureStackHeightWidget(); 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; if(objectId === -1) return null;
@ -25,7 +42,7 @@ export const FurnitureStackHeightView: FC<{}> = props =>
value={ height } value={ height }
onChange={ event => updateHeight(event) } onChange={ event => updateHeight(event) }
renderThumb={ (props, state) => <div { ...props }>{ state.valueNow }</div> } /> 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> </Flex>
<Column gap={ 1 }> <Column gap={ 1 }>
<Button onClick={ event => SendMessageComposer(new FurnitureStackHeightComposer(objectId, -100)) }> <Button onClick={ event => SendMessageComposer(new FurnitureStackHeightComposer(objectId, -100)) }>

View File

@ -5,6 +5,8 @@ import { useFurnitureStickieWidget } from '../../../../hooks';
const STICKIE_COLORS = [ '9CCEFF','FF9CFF', '9CFF9C','FFFF33' ]; const STICKIE_COLORS = [ '9CCEFF','FF9CFF', '9CFF9C','FFFF33' ];
const STICKIE_COLOR_NAMES = [ 'blue', 'pink', 'green', 'yellow' ]; 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) => const getStickieColorName = (color: string) =>
{ {
@ -15,31 +17,43 @@ const getStickieColorName = (color: string) =>
return STICKIE_COLOR_NAMES[index]; 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 => 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); const [ isEditing, setIsEditing ] = useState(false);
useEffect(() => useEffect(() =>
{ {
setIsEditing(false); setIsEditing(false);
}, [ objectId, color, text ]); }, [ objectId, color, text, type ]);
if(objectId === -1) return null; if(objectId === -1) return null;
return ( return (
<DraggableWindow handleSelector=".drag-handler" windowPosition={ DraggableWindowPosition.TOP_LEFT }> <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 stickie-header drag-handler">
<div className="d-flex align-items-center flex-grow-1 h-100"> <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> <div className="nitro-stickie-image stickie-trash header-trash" onClick={ trash }></div>
{ type == 'post_it' &&
<>
{ STICKIE_COLORS.map(color => { STICKIE_COLORS.map(color =>
{ {
return <div key={ color } className="stickie-color ms-1" onClick={ event => updateColor(color) } style={ { backgroundColor: ColorUtils.makeColorHex(color) } } /> return <div key={ color } className="stickie-color ms-1" onClick={ event => updateColor(color) } style={ { backgroundColor: ColorUtils.makeColorHex(color) } } />
}) } }) }
</> } </> }
</> }
</div> </div>
<div className="d-flex align-items-center nitro-stickie-image stickie-close header-close" onClick={ onClose }></div> <div className="d-flex align-items-center nitro-stickie-image stickie-close header-close" onClick={ onClose }></div>
</div> </div>

View File

@ -161,6 +161,26 @@
background-position: -2px -184px; 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 { &.stickie-close {
width: 10px; width: 10px;
height: 10px; height: 10px;

View File

@ -19,7 +19,7 @@ export const WiredBaseView: FC<PropsWithChildren<WiredBaseViewProps>> = props =>
const [ wiredName, setWiredName ] = useState<string>(null); const [ wiredName, setWiredName ] = useState<string>(null);
const [ wiredDescription, setWiredDescription ] = useState<string>(null); const [ wiredDescription, setWiredDescription ] = useState<string>(null);
const [ needsSave, setNeedsSave ] = useState<boolean>(false); 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); const onClose = () => setTrigger(null);
@ -83,6 +83,11 @@ export const WiredBaseView: FC<PropsWithChildren<WiredBaseViewProps>> = props =>
} }
}, [ trigger, hasSpecialInput, requiresFurni, setIntParams, setStringParam, setFurniIds ]); }, [ trigger, hasSpecialInput, requiresFurni, setIntParams, setStringParam, setFurniIds ]);
useEffect(() =>
{
setAllowsFurni(requiresFurni);
}, [ requiresFurni, setAllowsFurni ]);
return ( return (
<NitroCardView uniqueKey="nitro-wired" className="nitro-wired" theme="primary-slim"> <NitroCardView uniqueKey="nitro-wired" className="nitro-wired" theme="primary-slim">
<NitroCardHeaderView headerText={ LocalizeText('wiredfurni.title') } onCloseClick={ onClose } /> <NitroCardHeaderView headerText={ LocalizeText('wiredfurni.title') } onCloseClick={ onClose } />

View File

@ -1,5 +1,5 @@
import { FC, useEffect, useState } from 'react'; 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 { Column, Flex, Text } from '../../../../common';
import { useWired } from '../../../../hooks'; import { useWired } from '../../../../hooks';
import { WiredActionBaseView } from './WiredActionBaseView'; import { WiredActionBaseView } from './WiredActionBaseView';
@ -35,7 +35,7 @@ export const WiredActionBotTalkToAvatarView: FC<{}> = props =>
</Column> </Column>
<Column gap={ 1 }> <Column gap={ 1 }>
<Text bold>{ LocalizeText('wiredfurni.params.message') }</Text> <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>
<Column gap={ 1 }> <Column gap={ 1 }>
<Flex alignItems="center" gap={ 1 }> <Flex alignItems="center" gap={ 1 }>

View File

@ -1,5 +1,5 @@
import { FC, useEffect, useState } from 'react'; 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 { Column, Flex, Text } from '../../../../common';
import { useWired } from '../../../../hooks'; import { useWired } from '../../../../hooks';
import { WiredActionBaseView } from './WiredActionBaseView'; import { WiredActionBaseView } from './WiredActionBaseView';
@ -35,7 +35,7 @@ export const WiredActionBotTalkView: FC<{}> = props =>
</Column> </Column>
<Column gap={ 1 }> <Column gap={ 1 }>
<Text bold>{ LocalizeText('wiredfurni.params.message') }</Text> <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>
<Column gap={ 1 }> <Column gap={ 1 }>
<Flex alignItems="center" gap={ 1 }> <Flex alignItems="center" gap={ 1 }>

View File

@ -1,5 +1,5 @@
import { FC, useEffect, useState } from 'react'; import { FC, useEffect, useState } from 'react';
import { LocalizeText, WiredFurniType } from '../../../../api'; import { GetConfiguration, LocalizeText, WiredFurniType } from '../../../../api';
import { Column, Text } from '../../../../common'; import { Column, Text } from '../../../../common';
import { useWired } from '../../../../hooks'; import { useWired } from '../../../../hooks';
import { WiredActionBaseView } from './WiredActionBaseView'; import { WiredActionBaseView } from './WiredActionBaseView';
@ -20,7 +20,7 @@ export const WiredActionChatView: FC<{}> = props =>
<WiredActionBaseView requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_NONE } hasSpecialInput={ true } save={ save }> <WiredActionBaseView requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_NONE } hasSpecialInput={ true } save={ save }>
<Column gap={ 1 }> <Column gap={ 1 }>
<Text bold>{ LocalizeText('wiredfurni.params.message') }</Text> <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> </Column>
</WiredActionBaseView> </WiredActionBaseView>
); );

View File

@ -1,5 +1,5 @@
import { FC, useEffect, useState } from 'react'; import { FC, useEffect, useState } from 'react';
import { LocalizeText, WiredFurniType } from '../../../../api'; import { GetConfiguration, LocalizeText, WiredFurniType } from '../../../../api';
import { Column, Text } from '../../../../common'; import { Column, Text } from '../../../../common';
import { useWired } from '../../../../hooks'; import { useWired } from '../../../../hooks';
import { WiredActionBaseView } from './WiredActionBaseView'; import { WiredActionBaseView } from './WiredActionBaseView';
@ -20,7 +20,7 @@ export const WiredActionKickFromRoomView: FC<{}> = props =>
<WiredActionBaseView requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_NONE } hasSpecialInput={ true } save={ save }> <WiredActionBaseView requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_NONE } hasSpecialInput={ true } save={ save }>
<Column gap={ 1 }> <Column gap={ 1 }>
<Text bold>{ LocalizeText('wiredfurni.params.message') }</Text> <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> </Column>
</WiredActionBaseView> </WiredActionBaseView>
); );

View File

@ -1,6 +1,6 @@
import { FC, useEffect, useState } from 'react'; import { FC, useEffect, useState } from 'react';
import ReactSlider from 'react-slider'; import ReactSlider from 'react-slider';
import { LocalizeText, WiredFurniType } from '../../../../api'; import { GetConfiguration, LocalizeText, WiredFurniType } from '../../../../api';
import { Column, Text } from '../../../../common'; import { Column, Text } from '../../../../common';
import { useWired } from '../../../../hooks'; import { useWired } from '../../../../hooks';
import { WiredActionBaseView } from './WiredActionBaseView'; import { WiredActionBaseView } from './WiredActionBaseView';
@ -36,7 +36,7 @@ export const WiredActionMuteUserView: FC<{}> = props =>
</Column> </Column>
<Column gap={ 1 }> <Column gap={ 1 }>
<Text bold>{ LocalizeText('wiredfurni.params.message') }</Text> <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> </Column>
</WiredActionBaseView> </WiredActionBaseView>
); );

View File

@ -875,6 +875,13 @@ const useCatalogState = () =>
if(!searchResult && currentPage && (currentPage.pageId === -1)) openPageById(previousPageId); if(!searchResult && currentPage && (currentPage.pageId === -1)) openPageById(previousPageId);
}, [ searchResult, currentPage, previousPageId, openPageById ]); }, [ searchResult, currentPage, previousPageId, openPageById ]);
useEffect(() =>
{
if(!currentOffer) return;
setPurchaseOptions({ quantity: 1, extraData: null, extraParamRequired: false, previewStuffData: null });
}, [ currentOffer ]);
useEffect(() => useEffect(() =>
{ {
if(!isVisible || rootNode) return; if(!isVisible || rootNode) return;

View File

@ -10,6 +10,7 @@ const useFurnitureStickieWidgetState = () =>
const [ category, setCategory ] = useState(-1); const [ category, setCategory ] = useState(-1);
const [ color, setColor ] = useState('0'); const [ color, setColor ] = useState('0');
const [ text, setText ] = useState(''); const [ text, setText ] = useState('');
const [ type, setType ] = useState('');
const [ canModify, setCanModify ] = useState(false); const [ canModify, setCanModify ] = useState(false);
const onClose = () => const onClose = () =>
@ -18,6 +19,7 @@ const useFurnitureStickieWidgetState = () =>
setCategory(-1); setCategory(-1);
setColor('0'); setColor('0');
setText(''); setText('');
setType('');
setCanModify(false); setCanModify(false);
} }
@ -66,6 +68,7 @@ const useFurnitureStickieWidgetState = () =>
setCategory(event.category); setCategory(event.category);
setColor(color || '0'); setColor(color || '0');
setText(text || ''); setText(text || '');
setType(roomObject.type || 'post_it');
setCanModify(GetRoomSession().isRoomOwner || GetSessionDataManager().isModerator || IsOwnerOfFurniture(roomObject)); setCanModify(GetRoomSession().isRoomOwner || GetSessionDataManager().isModerator || IsOwnerOfFurniture(roomObject));
}); });
@ -76,7 +79,7 @@ const useFurnitureStickieWidgetState = () =>
onClose(); onClose();
}); });
return { objectId, color, text, canModify, updateColor, updateText, trash, onClose }; return { objectId, color, text, type, canModify, updateColor, updateText, trash, onClose };
} }
export const useFurnitureStickieWidget = useFurnitureStickieWidgetState; export const useFurnitureStickieWidget = useFurnitureStickieWidgetState;

View File

@ -1,7 +1,7 @@
import { FigureUpdateEvent, RoomUnitChatStyleComposer, UserInfoDataParser, UserInfoEvent, UserSettingsEvent } from '@nitrots/nitro-renderer'; import { FigureUpdateEvent, RoomUnitChatStyleComposer, UserInfoDataParser, UserInfoEvent, UserSettingsEvent } from '@nitrots/nitro-renderer';
import { useState } from 'react'; import { useState } from 'react';
import { useBetween } from 'use-between'; import { useBetween } from 'use-between';
import { SendMessageComposer } from '../../api'; import { GetSessionDataManager, SendMessageComposer } from '../../api';
import { useMessageEvent } from '../events'; import { useMessageEvent } from '../events';
const useSessionInfoState = () => const useSessionInfoState = () =>
@ -9,6 +9,8 @@ const useSessionInfoState = () =>
const [ userInfo, setUserInfo ] = useState<UserInfoDataParser>(null); const [ userInfo, setUserInfo ] = useState<UserInfoDataParser>(null);
const [ userFigure, setUserFigure ] = useState<string>(null); const [ userFigure, setUserFigure ] = useState<string>(null);
const [ chatStyleId, setChatStyleId ] = useState<number>(0); const [ chatStyleId, setChatStyleId ] = useState<number>(0);
const [ userRespectRemaining, setUserRespectRemaining ] = useState<number>(0);
const [ petRespectRemaining, setPetRespectRemaining ] = useState<number>(0);
const updateChatStyleId = (styleId: number) => const updateChatStyleId = (styleId: number) =>
{ {
@ -17,12 +19,28 @@ const useSessionInfoState = () =>
SendMessageComposer(new RoomUnitChatStyleComposer(styleId)); 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 => useMessageEvent<UserInfoEvent>(UserInfoEvent, event =>
{ {
const parser = event.getParser(); const parser = event.getParser();
setUserInfo(parser.userInfo); setUserInfo(parser.userInfo);
setUserFigure(parser.userInfo.figure); setUserFigure(parser.userInfo.figure);
setUserRespectRemaining(parser.userInfo.respectsRemaining);
setPetRespectRemaining(parser.userInfo.respectsPetRemaining);
}); });
useMessageEvent<FigureUpdateEvent>(FigureUpdateEvent, event => useMessageEvent<FigureUpdateEvent>(FigureUpdateEvent, event =>
@ -39,7 +57,7 @@ const useSessionInfoState = () =>
setChatStyleId(parser.chatType); setChatStyleId(parser.chatType);
}); });
return { userInfo, userFigure, chatStyleId, updateChatStyleId }; return { userInfo, userFigure, chatStyleId, userRespectRemaining, petRespectRemaining, respectUser, respectPet, updateChatStyleId };
} }
export const useSessionInfo = () => useBetween(useSessionInfoState); export const useSessionInfo = () => useBetween(useSessionInfoState);

View File

@ -1,7 +1,7 @@
import { ConditionDefinition, Triggerable, TriggerDefinition, UpdateActionMessageComposer, UpdateConditionMessageComposer, UpdateTriggerMessageComposer, WiredActionDefinition, WiredFurniActionEvent, WiredFurniConditionEvent, WiredFurniTriggerEvent, WiredSaveSuccessEvent } from '@nitrots/nitro-renderer'; import { ConditionDefinition, Triggerable, TriggerDefinition, UpdateActionMessageComposer, UpdateConditionMessageComposer, UpdateTriggerMessageComposer, WiredActionDefinition, WiredFurniActionEvent, WiredFurniConditionEvent, WiredFurniTriggerEvent, WiredSaveSuccessEvent } from '@nitrots/nitro-renderer';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { useBetween } from 'use-between'; 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 { useMessageEvent } from '../events';
import { useNotification } from '../notification'; import { useNotification } from '../notification';
@ -12,6 +12,7 @@ const useWiredState = () =>
const [ stringParam, setStringParam ] = useState<string>(''); const [ stringParam, setStringParam ] = useState<string>('');
const [ furniIds, setFurniIds ] = useState<number[]>([]); const [ furniIds, setFurniIds ] = useState<number[]>([]);
const [ actionDelay, setActionDelay ] = useState<number>(0); const [ actionDelay, setActionDelay ] = useState<number>(0);
const [ allowsFurni, setAllowsFurni ] = useState<number>(WiredFurniType.STUFF_SELECTION_OPTION_NONE);
const { showConfirm = null } = useNotification(); const { showConfirm = null } = useNotification();
const saveWired = () => const saveWired = () =>
@ -51,7 +52,7 @@ const useWiredState = () =>
const selectObjectForWired = (objectId: number, category: number) => const selectObjectForWired = (objectId: number, category: number) =>
{ {
if(!trigger) return; if(!trigger || !allowsFurni) return;
if(objectId <= 0) return; if(objectId <= 0) return;
@ -122,10 +123,11 @@ const useWiredState = () =>
return []; return [];
}); });
setAllowsFurni(WiredFurniType.STUFF_SELECTION_OPTION_NONE);
} }
}, [ trigger ]); }, [ 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); export const useWired = () => useBetween(useWiredState);