diff --git a/package.json b/package.json index 8e6a95fd..9b5a435b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,7 @@ { "name": "nitro-react", "version": "2.1.0", + "homepage": ".", "private": true, "scripts": { "start": "cross-env SKIP_PREFLIGHT_CHECK=true BROWSER=none IMAGE_INLINE_SIZE_LIMIT=100000 craco --openssl-legacy-provider start", diff --git a/public/index.html b/public/index.html index d4019e89..aaceef80 100644 --- a/public/index.html +++ b/public/index.html @@ -15,6 +15,7 @@ + Nitro diff --git a/public/ui-config.json.example b/public/ui-config.json.example index 403bb68a..def823b3 100644 --- a/public/ui-config.json.example +++ b/public/ui-config.json.example @@ -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" }, diff --git a/src/assets/images/room-widgets/stickie-widget/stickie-dreams.png b/src/assets/images/room-widgets/stickie-widget/stickie-dreams.png new file mode 100644 index 00000000..1723bdcb Binary files /dev/null and b/src/assets/images/room-widgets/stickie-widget/stickie-dreams.png differ diff --git a/src/assets/images/room-widgets/stickie-widget/stickie-heart.png b/src/assets/images/room-widgets/stickie-widget/stickie-heart.png new file mode 100644 index 00000000..45523851 Binary files /dev/null and b/src/assets/images/room-widgets/stickie-widget/stickie-heart.png differ diff --git a/src/assets/images/room-widgets/stickie-widget/stickie-juninas.png b/src/assets/images/room-widgets/stickie-widget/stickie-juninas.png new file mode 100644 index 00000000..faaea9d1 Binary files /dev/null and b/src/assets/images/room-widgets/stickie-widget/stickie-juninas.png differ diff --git a/src/assets/images/room-widgets/stickie-widget/stickie-shakesp.png b/src/assets/images/room-widgets/stickie-widget/stickie-shakesp.png new file mode 100644 index 00000000..d5011c74 Binary files /dev/null and b/src/assets/images/room-widgets/stickie-widget/stickie-shakesp.png differ diff --git a/src/common/index.scss b/src/common/index.scss index 9178bec6..b9e5297c 100644 --- a/src/common/index.scss +++ b/src/common/index.scss @@ -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); } @@ -388,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; } @@ -408,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; } } @@ -451,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; @@ -548,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 { diff --git a/src/components/avatar-editor/views/figure-set/AvatarEditorFigureSetItemView.tsx b/src/components/avatar-editor/views/figure-set/AvatarEditorFigureSetItemView.tsx index 8df5b95d..fd28dc55 100644 --- a/src/components/avatar-editor/views/figure-set/AvatarEditorFigureSetItemView.tsx +++ b/src/components/avatar-editor/views/figure-set/AvatarEditorFigureSetItemView.tsx @@ -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('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 ( diff --git a/src/components/avatar-editor/views/figure-set/AvatarEditorFigureSetView.tsx b/src/components/avatar-editor/views/figure-set/AvatarEditorFigureSetView.tsx index 8a813a03..3755731c 100644 --- a/src/components/avatar-editor/views/figure-set/AvatarEditorFigureSetView.tsx +++ b/src/components/avatar-editor/views/figure-set/AvatarEditorFigureSetView.tsx @@ -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 = props => { const { model = null, category = null, setMaxPaletteCount = null } = props; + const elementRef = useRef(null); const selectPart = useCallback((item: AvatarEditorGridPartItem) => { @@ -27,8 +28,15 @@ export const AvatarEditorFigureSetView: FC = pro setMaxPaletteCount(partItem.maxColorIndex || 1); }, [ model, category, setMaxPaletteCount ]); + useEffect(() => + { + if(!model || !category || !elementRef || !elementRef.current) return; + + elementRef.current.scrollTop = 0; + }, [ model, category ]); + return ( - + { (category.parts.length > 0) && category.parts.map((item, index) => selectPart(item) } />) } diff --git a/src/components/avatar-editor/views/palette-set/AvatarEditorPaletteSetItemView.tsx b/src/components/avatar-editor/views/palette-set/AvatarEditorPaletteSetItemView.tsx index 8138e951..638a9d18 100644 --- a/src/components/avatar-editor/views/palette-set/AvatarEditorPaletteSetItemView.tsx +++ b/src/components/avatar-editor/views/palette-set/AvatarEditorPaletteSetItemView.tsx @@ -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 = p const hcDisabled = GetConfiguration('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 ( diff --git a/src/components/avatar-editor/views/palette-set/AvatarEditorPaletteSetView.tsx b/src/components/avatar-editor/views/palette-set/AvatarEditorPaletteSetView.tsx index 4ed984be..c55dcb47 100644 --- a/src/components/avatar-editor/views/palette-set/AvatarEditorPaletteSetView.tsx +++ b/src/components/avatar-editor/views/palette-set/AvatarEditorPaletteSetView.tsx @@ -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 = props => { const { model = null, category = null, paletteSet = [], paletteIndex = -1 } = props; + const elementRef = useRef(null); const selectColor = useCallback((item: AvatarEditorGridColorItem) => { @@ -24,8 +25,15 @@ export const AvatarEditorPaletteSetView: FC = 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 ( - + { (paletteSet.length > 0) && paletteSet.map((item, index) => selectColor(item) } />) } diff --git a/src/components/catalog/views/page/widgets/CatalogPurchaseWidgetView.tsx b/src/components/catalog/views/page/widgets/CatalogPurchaseWidgetView.tsx index 5cca1d29..83190db0 100644 --- a/src/components/catalog/views/page/widgets/CatalogPurchaseWidgetView.tsx +++ b/src/components/catalog/views/page/widgets/CatalogPurchaseWidgetView.tsx @@ -103,11 +103,7 @@ export const CatalogPurchaseWidgetView: FC = pro { if(!currentOffer) return; - return () => - { - setPurchaseState(CatalogPurchaseState.NONE); - setPurchaseOptions({ quantity: 1, extraData: null, extraParamRequired: false, previewStuffData: null }); - } + setPurchaseState(CatalogPurchaseState.NONE); }, [ currentOffer, setPurchaseOptions ]); useEffect(() => diff --git a/src/components/guide-tool/views/GuideToolOngoingView.tsx b/src/components/guide-tool/views/GuideToolOngoingView.tsx index 7dd01850..080de332 100644 --- a/src/components/guide-tool/views/GuideToolOngoingView.tsx +++ b/src/components/guide-tool/views/GuideToolOngoingView.tsx @@ -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 = props => { + const scrollDiv = useRef(null); + const { isGuide = false, userId = 0, userName = null, userFigure = null, isTyping = false, messageGroups = [] } = props; const [ messageText, setMessageText ] = useState(''); + useEffect(() => + { + scrollDiv.current?.scrollIntoView({ block: 'end', behavior: 'smooth' }); + + }, [ messageGroups ]); + const visit = useCallback(() => { SendMessageComposer(new GuideSessionGetRequesterRoomMessageComposer()); @@ -38,7 +46,7 @@ export const GuideToolOngoingView: FC = props => useMessageEvent(GuideSessionRequesterRoomMessageEvent, event => { const parser = event.getParser(); - + TryVisitRoom(parser.requesterRoomId); }); @@ -100,7 +108,8 @@ export const GuideToolOngoingView: FC = props => } ); - }) } + }) } +
diff --git a/src/components/help/HelpView.tsx b/src/components/help/HelpView.tsx index 7a257be7..e0df417c 100644 --- a/src/components/help/HelpView.tsx +++ b/src/components/help/HelpView.tsx @@ -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 ( <> - - - - - - - - - - - - - + { isVisible && + + + + + + + + + + + + + } diff --git a/src/components/help/views/SelectReportedChatsView.tsx b/src/components/help/views/SelectReportedChatsView.tsx index d83e6a02..0dfba492 100644 --- a/src/components/help/views/SelectReportedChatsView.tsx +++ b/src/components/help/views/SelectReportedChatsView.tsx @@ -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 => { LocalizeText('help.emergency.chat_report.description') } - { !!!userChats.length && + { !userChats || !userChats.length && { LocalizeText('help.cfh.error.no_user_data') } } { (userChats.length > 0) && diff --git a/src/components/help/views/name-change/NameChangeView.tsx b/src/components/help/views/name-change/NameChangeView.tsx index d4959efc..5cc76a9a 100644 --- a/src/components/help/views/name-change/NameChangeView.tsx +++ b/src/components/help/views/name-change/NameChangeView.tsx @@ -17,13 +17,11 @@ export const NameChangeView:FC<{}> = props => const [ layout, setLayout ] = useState(INIT); const [ newUsername, setNewUsername ] = useState(''); - const onHelpNameChangeEvent = useCallback((event: HelpNameChangeEvent) => + useUiEvent(HelpNameChangeEvent.INIT, event => { setLayout(INIT); setIsVisible(true); - }, []); - - useUiEvent(HelpNameChangeEvent.INIT, onHelpNameChangeEvent); + }); const onAction = useCallback((action: string, value?: string) => { diff --git a/src/components/inventory/views/badge/InventoryBadgeItemView.tsx b/src/components/inventory/views/badge/InventoryBadgeItemView.tsx index e5bfb71c..bb6c54af 100644 --- a/src/components/inventory/views/badge/InventoryBadgeItemView.tsx +++ b/src/components/inventory/views/badge/InventoryBadgeItemView.tsx @@ -6,14 +6,14 @@ import { useInventoryBadges, useInventoryUnseenTracker } from '../../../../hooks export const InventoryBadgeItemView: FC> = 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 ( - setSelectedBadgeCode(badgeCode) } { ...rest }> + setSelectedBadgeCode(badgeCode) } onDoubleClick={ event => toggleBadge(selectedBadgeCode) } { ...rest }> { children } ); -} +} \ No newline at end of file diff --git a/src/components/inventory/views/bot/InventoryBotItemView.tsx b/src/components/inventory/views/bot/InventoryBotItemView.tsx index d2a15cf4..d2130693 100644 --- a/src/components/inventory/views/bot/InventoryBotItemView.tsx +++ b/src/components/inventory/views/bot/InventoryBotItemView.tsx @@ -26,13 +26,16 @@ export const InventoryBotItemView: FC> case MouseEventType.ROLL_OUT: if(!isMouseDown || (selectedBot !== botItem)) return; + attemptBotPlacement(botItem); + return; + case 'dblclick': attemptBotPlacement(botItem); return; } } return ( - + { children } diff --git a/src/components/inventory/views/furniture/InventoryFurnitureItemView.tsx b/src/components/inventory/views/furniture/InventoryFurnitureItemView.tsx index f72c6ab5..f5e84814 100644 --- a/src/components/inventory/views/furniture/InventoryFurnitureItemView.tsx +++ b/src/components/inventory/views/furniture/InventoryFurnitureItemView.tsx @@ -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 ; + return ; } diff --git a/src/components/inventory/views/furniture/InventoryTradeView.tsx b/src/components/inventory/views/furniture/InventoryTradeView.tsx index 3de9eef8..77337fcd 100644 --- a/src/components/inventory/views/furniture/InventoryTradeView.tsx +++ b/src/components/inventory/views/furniture/InventoryTradeView.tsx @@ -21,6 +21,7 @@ export const InventoryTradeView: FC = props => const [ otherGroupItem, setOtherGroupItem ] = useState(null); const [ filteredGroupItems, setFilteredGroupItems ] = useState(null); const [ countdownTick, setCountdownTick ] = useState(3); + const [ quantity, setQuantity ] = useState(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 = props => return } + 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 = props => const count = item.getUnlockedCount(); return ( - (count && setGroupItem(item)) }> + (count && setGroupItem(item)) } onDoubleClick={ event => attemptItemOffer(1) }> { ((count > 0) && (groupItem === item)) && - } + + } ); }) } - - { groupItem ? groupItem.name : LocalizeText('catalog_selectproduct') } - + + + + setQuantity(event.target.valueAsNumber) } /> + + + + + + + { groupItem ? groupItem.name : LocalizeText('catalog_selectproduct') } + + @@ -188,11 +223,11 @@ export const InventoryTradeView: FC = props => if(!item) return ; return ( - setOwnGroupItem(item) }> + setOwnGroupItem(item) } onDoubleClick={ event => removeItem(item) }> { (ownGroupItem === item) && - } + } ); }) } diff --git a/src/components/inventory/views/pet/InventoryPetItemView.tsx b/src/components/inventory/views/pet/InventoryPetItemView.tsx index 41b0619e..aad45f95 100644 --- a/src/components/inventory/views/pet/InventoryPetItemView.tsx +++ b/src/components/inventory/views/pet/InventoryPetItemView.tsx @@ -26,13 +26,16 @@ export const InventoryPetItemView: FC> case MouseEventType.ROLL_OUT: if(!isMouseDown || !(petItem === selectedPet)) return; + attemptPetPlacement(petItem); + return; + case 'dblclick': attemptPetPlacement(petItem); return; } } - + return ( - + { children } diff --git a/src/components/room/widgets/avatar-info/infostand/InfoStandWidgetPetView.tsx b/src/components/room/widgets/avatar-info/infostand/InfoStandWidgetPetView.tsx index f7a6792f..f3f2f5db 100644 --- a/src/components/room/widgets/avatar-info/infostand/InfoStandWidgetPetView.tsx +++ b/src/components/room/widgets/avatar-info/infostand/InfoStandWidgetPetView.tsx @@ -1,9 +1,9 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { PetRespectComposer, PetType } from '@nitrots/nitro-renderer'; import { FC, useEffect, useState } from 'react'; -import { AvatarInfoPet, ConvertSeconds, CreateLinkEvent, GetConfiguration, GetSessionDataManager, LocalizeText, SendMessageComposer } from '../../../../../api'; -import { Base, Button, Column, Flex, LayoutCounterTimeView, LayoutPetImageView, LayoutRarityLevelView, Text, UserProfileIconView } from '../../../../../common'; -import { usePets, useRoom } from '../../../../../hooks'; +import { AvatarInfoPet, CreateLinkEvent, GetConfiguration, LocalizeText, SendMessageComposer, ConvertSeconds } from '../../../../../api'; +import { Base, Button, Column, Flex, LayoutPetImageView, LayoutRarityLevelView, Text, UserProfileIconView, LayoutCounterTimeView } from '../../../../../common'; +import { useRoom, useSessionInfo } from '../../../../../hooks'; interface InfoStandWidgetPetViewProps { @@ -14,21 +14,32 @@ interface InfoStandWidgetPetViewProps export const InfoStandWidgetPetView: FC = props => { const { avatarInfo = null, onClose = null } = props; - const { roomSession = null } = useRoom(); - const { petRespect, changePetRespect } = usePets(); const [ remainingGrowTime, setRemainingGrowTime ] = useState(0); const [ remainingTimeToLive, setRemainingTimeToLive ] = useState(0); - - if(!avatarInfo) return null; + const { roomSession = null } = useRoom(); + const { petRespectRemaining = 0, respectPet = null } = useSessionInfo(); useEffect(() => { - changePetRespect(avatarInfo.respectsPetLeft); 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; @@ -38,17 +49,9 @@ export const InfoStandWidgetPetView: FC = props => switch (action) { case 'respect': - let newRespectsLeftChange = 0; + respectPet(avatarInfo.id); - changePetRespect(prevValue => - { - newRespectsLeftChange = (prevValue - 1); - - return newRespectsLeftChange; - }); - - GetSessionDataManager().givePetRespect(avatarInfo.id); - if(newRespectsLeftChange > 0) hideMenu = false; + if((petRespectRemaining - 1) >= 1) hideMenu = false; break; case 'buyfood': CreateLinkEvent('catalog/open/' + GetConfiguration('catalog.links')['pets.buy_saddle']); @@ -70,229 +73,143 @@ export const InfoStandWidgetPetView: FC = props => 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 ( - - - - - - { avatarInfo.name } - - - { LocalizeText(`pet.breed.${ avatarInfo.petType }.${ avatarInfo.petBreed }`) } -
-
- - - - - - - { LocalizeText('pet.level', [ 'level', 'maxlevel' ], [ avatarInfo.level.toString(), avatarInfo.maximumLevel.toString() ]) } - - { LocalizeText('infostand.pet.text.happiness') } - - - { avatarInfo.happyness + '/' + avatarInfo.maximumHappyness } - - - - - - { LocalizeText('infostand.pet.text.experience') } - - - { avatarInfo.experience + '/' + avatarInfo.levelExperienceGoal } - - - - - - { LocalizeText('infostand.pet.text.energy') } - - - { avatarInfo.energy + '/' + avatarInfo.maximumEnergy } - - - - - - -
-
- - { LocalizeText('pet.age', [ 'age' ], [ avatarInfo.age.toString() ]) } -
-
- - - - - { LocalizeText('infostand.text.petowner', [ 'name' ], [ avatarInfo.ownerName ]) } - - - + return ( + + + + + + { avatarInfo.name } + + + { LocalizeText(`pet.breed.${ avatarInfo.petType }.${ avatarInfo.petBreed }`) } +
-
- - - { avatarInfo.isOwner && - - } - { avatarInfo.isOwner && - - } - { (petRespect > 0) && - - } - -
- ); - } - - const InfoStandMonsterplantPet = () => - { - return ( - - - - - - { avatarInfo.name } - - - { LocalizeText(`pet.breed.${ avatarInfo.petType }.${ avatarInfo.petBreed }`) } -
-
- - - - + { (avatarInfo.petType === PetType.MONSTERPLANT) && + <> + + + + + + { !avatarInfo.dead && + + { LocalizeText('pet.level', [ 'level', 'maxlevel' ], [ avatarInfo.level.toString(), avatarInfo.maximumLevel.toString() ]) } + } + +
+
+ + + { LocalizeText('infostand.pet.text.wellbeing') } + + + { 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] } + + + - { !avatarInfo.dead && +

+

+ { remainingGrowTime != 0 && remainingGrowTime > 0 && + + { LocalizeText('infostand.pet.text.growth') }
+ +
} +

+

+ + { LocalizeText('Nivel de rareza:') } + + +

+ { LocalizeText('pet.age', [ 'age' ], [ avatarInfo.age.toString() ]) } +
+
+ } + { (avatarInfo.petType !== PetType.MONSTERPLANT) && + <> + + + + + { LocalizeText('pet.level', [ 'level', 'maxlevel' ], [ avatarInfo.level.toString(), avatarInfo.maximumLevel.toString() ]) } + + { LocalizeText('infostand.pet.text.happiness') } + + + { avatarInfo.happyness + '/' + avatarInfo.maximumHappyness } + + + + + + { LocalizeText('infostand.pet.text.experience') } + + + { avatarInfo.experience + '/' + avatarInfo.levelExperienceGoal } + + + + + + { LocalizeText('infostand.pet.text.energy') } + + + { avatarInfo.energy + '/' + avatarInfo.maximumEnergy } + + + + - } - -
-
-

- - - { LocalizeText('infostand.pet.text.wellbeing') } - - - { 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] } - - - +
+
-

-

- { remainingGrowTime != 0 && remainingGrowTime > 0 && - - { LocalizeText('infostand.pet.text.growth') }
- -
- } -

-

- - { LocalizeText('Nivel de rareza:') } - + + { (avatarInfo.petType !== PetType.MONSTERPLANT) && + { LocalizeText('infostand.text.petrespect', [ 'count' ], [ avatarInfo.respect.toString() ]) } } + { LocalizeText('pet.age', [ 'age' ], [ avatarInfo.age.toString() ]) } +
-

- { LocalizeText('pet.age', [ 'age' ], [ avatarInfo.age.toString() ]) } -
-
- - - - - { LocalizeText('infostand.text.petowner', [ 'name' ], [ avatarInfo.ownerName ]) } - - - + } + + + + + { LocalizeText('infostand.text.petowner', [ 'name' ], [ avatarInfo.ownerName ]) } + +
- - { !avatarInfo.dead && ((avatarInfo.energy / avatarInfo.maximumEnergy) < 0.98) && - - } - { roomSession?.isRoomOwner && - - } - { avatarInfo.isOwner && - - } -
- ); - } - - return ( - <> - { avatarInfo.petType !== PetType.MONSTERPLANT && - - } - { avatarInfo.petType === PetType.MONSTERPLANT && - - } - + + { (avatarInfo.petType !== PetType.MONSTERPLANT) && + } + { avatarInfo.isOwner && (avatarInfo.petType !== PetType.MONSTERPLANT) && + } + { !avatarInfo.dead && ((avatarInfo.energy / avatarInfo.maximumEnergy) < 0.98) && (avatarInfo.petType === PetType.MONSTERPLANT) && + } + { roomSession?.isRoomOwner && (avatarInfo.petType === PetType.MONSTERPLANT) && + } + { avatarInfo.isOwner && + } + { (petRespectRemaining > 0) && (avatarInfo.petType !== PetType.MONSTERPLANT) && + } + +
); } diff --git a/src/components/room/widgets/avatar-info/menu/AvatarInfoWidgetAvatarView.tsx b/src/components/room/widgets/avatar-info/menu/AvatarInfoWidgetAvatarView.tsx index 3a4185aa..9556888e 100644 --- a/src/components/room/widgets/avatar-info/menu/AvatarInfoWidgetAvatarView.tsx +++ b/src/components/room/widgets/avatar-info/menu/AvatarInfoWidgetAvatarView.tsx @@ -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 = 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 = 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 = p useEffect(() => { setMode(MODE_NORMAL); - setRespectsLeft(avatarInfo.respectLeft); }, [ avatarInfo ]); return ( @@ -223,9 +218,9 @@ export const AvatarInfoWidgetAvatarView: FC = p processAction('whisper') }> { LocalizeText('infostand.button.whisper') } - { (respectsLeft > 0) && + { (userRespectRemaining > 0) && processAction('respect') }> - { LocalizeText('infostand.button.respect', [ 'count' ], [ respectsLeft.toString() ]) } + { LocalizeText('infostand.button.respect', [ 'count' ], [ userRespectRemaining.toString() ]) } } { !canRequestFriend(avatarInfo.webID) && processAction('relationship') }> diff --git a/src/components/room/widgets/avatar-info/menu/AvatarInfoWidgetOwnPetView.tsx b/src/components/room/widgets/avatar-info/menu/AvatarInfoWidgetOwnPetView.tsx index 107393b5..af889c12 100644 --- a/src/components/room/widgets/avatar-info/menu/AvatarInfoWidgetOwnPetView.tsx +++ b/src/components/room/widgets/avatar-info/menu/AvatarInfoWidgetOwnPetView.tsx @@ -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 = 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 = 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 = p return MODE_NORMAL; }); - - setRespectsLeft(avatarInfo.respectsPetLeft); }, [ avatarInfo ]); return ( @@ -142,9 +131,9 @@ export const AvatarInfoWidgetOwnPetView: FC = p { (mode === MODE_NORMAL) && <> - { (respectsLeft > 0) && + { (petRespectRemaining > 0) && processAction('respect') }> - { LocalizeText('infostand.button.petrespect', [ 'count' ], [ respectsLeft.toString() ]) } + { LocalizeText('infostand.button.petrespect', [ 'count' ], [ petRespectRemaining.toString() ]) } } processAction('train') }> { LocalizeText('infostand.button.train') } @@ -170,9 +159,9 @@ export const AvatarInfoWidgetOwnPetView: FC = p { LocalizeText('infostand.button.toggle_riding_permission') } - { (respectsLeft > 0) && + { (petRespectRemaining > 0) && processAction('respect') }> - { LocalizeText('infostand.button.petrespect', [ 'count' ], [ respectsLeft.toString() ]) } + { LocalizeText('infostand.button.petrespect', [ 'count' ], [ petRespectRemaining.toString() ]) } } processAction('train') }> { LocalizeText('infostand.button.train') } @@ -189,9 +178,9 @@ export const AvatarInfoWidgetOwnPetView: FC = p processAction('dismount') }> { LocalizeText('infostand.button.dismount') } - { (respectsLeft > 0) && + { (petRespectRemaining > 0) && processAction('respect') }> - { LocalizeText('infostand.button.petrespect', [ 'count' ], [ respectsLeft.toString() ]) } + { LocalizeText('infostand.button.petrespect', [ 'count' ], [ petRespectRemaining.toString() ]) } } } { (mode === MODE_MONSTER_PLANT) && @@ -209,7 +198,7 @@ export const AvatarInfoWidgetOwnPetView: FC = p } { !avatarInfo.dead && ((avatarInfo.energy / avatarInfo.maximumEnergy) < 0.98) && processAction('treat') }> - { LocalizeText('infostand.button.treat') } + { LocalizeText('infostand.button.pettreat') } } { !avatarInfo.dead && (avatarInfo.level === avatarInfo.maximumLevel) && avatarInfo.breedable && <> diff --git a/src/components/room/widgets/avatar-info/menu/AvatarInfoWidgetPetView.tsx b/src/components/room/widgets/avatar-info/menu/AvatarInfoWidgetPetView.tsx index a4cc6721..4219ace9 100644 --- a/src/components/room/widgets/avatar-info/menu/AvatarInfoWidgetPetView.tsx +++ b/src/components/room/widgets/avatar-info/menu/AvatarInfoWidgetPetView.tsx @@ -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 = 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 = 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 = props = return MODE_NORMAL; }); - - setRespectsLeft(avatarInfo.respectsPetLeft); }, [ avatarInfo ]); return ( @@ -107,9 +96,9 @@ export const AvatarInfoWidgetPetView: FC = props = { avatarInfo.name } - { (mode === MODE_NORMAL) && (respectsLeft > 0) && + { (mode === MODE_NORMAL) && (petRespectRemaining > 0) && processAction('respect') }> - { LocalizeText('infostand.button.petrespect', [ 'count' ], [ respectsLeft.toString() ]) } + { LocalizeText('infostand.button.petrespect', [ 'count' ], [ petRespectRemaining.toString() ]) } } { (mode === MODE_SADDLED_UP) && <> @@ -117,9 +106,9 @@ export const AvatarInfoWidgetPetView: FC = props = processAction('mount') }> { LocalizeText('infostand.button.mount') } } - { (respectsLeft > 0) && + { (petRespectRemaining > 0) && processAction('respect') }> - { LocalizeText('infostand.button.petrespect', [ 'count' ], [ respectsLeft.toString() ]) } + { LocalizeText('infostand.button.petrespect', [ 'count' ], [ petRespectRemaining.toString() ]) } } } { (mode === MODE_RIDING) && @@ -127,14 +116,14 @@ export const AvatarInfoWidgetPetView: FC = props = processAction('dismount') }> { LocalizeText('infostand.button.dismount') } - { (respectsLeft > 0) && + { (petRespectRemaining > 0) && processAction('respect') }> - { LocalizeText('infostand.button.petrespect', [ 'count' ], [ respectsLeft.toString() ]) } + { LocalizeText('infostand.button.petrespect', [ 'count' ], [ petRespectRemaining.toString() ]) } } } { (mode === MODE_MONSTER_PLANT) && !avatarInfo.dead && ((avatarInfo.energy / avatarInfo.maximumEnergy) < 0.98) && processAction('treat') }> - { LocalizeText('infostand.button.treat') } + { LocalizeText('infostand.button.pettreat') } } { canPickUp && processAction('pick_up') }> diff --git a/src/components/room/widgets/chat-input/ChatInputView.tsx b/src/components/room/widgets/chat-input/ChatInputView.tsx index dcfb514e..f5db0f43 100644 --- a/src/components/room/widgets/chat-input/ChatInputView.tsx +++ b/src/components/room/widgets/chat-input/ChatInputView.tsx @@ -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.CHAT_INPUT_CONTENT, event => diff --git a/src/components/room/widgets/furniture/FurnitureStackHeightView.tsx b/src/components/room/widgets/furniture/FurnitureStackHeightView.tsx index f3a73b6c..d4d1039f 100644 --- a/src/components/room/widgets/furniture/FurnitureStackHeightView.tsx +++ b/src/components/room/widgets/furniture/FurnitureStackHeightView.tsx @@ -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) =>
{ state.valueNow }
} /> - updateHeight(parseFloat(event.target.value)) } /> + updateTempHeight(event.target.value) } />