diff --git a/package.json b/package.json index 8e6a95fd..cb577f5f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,7 @@ { "name": "nitro-react", - "version": "2.1.0", + "version": "2.1.1", + "homepage": ".", "private": true, "scripts": { "start": "cross-env SKIP_PREFLIGHT_CHECK=true BROWSER=none IMAGE_INLINE_SIZE_LIMIT=100000 craco --openssl-legacy-provider start", 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..80b6970a 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" }, @@ -120,6 +125,8 @@ "catalog.asset.url": "${image.library.url}catalogue", "catalog.asset.image.url": "${catalog.asset.url}/%name%.gif", "catalog.asset.icon.url": "${catalog.asset.url}/icon_%name%.png", + "catalog.tab.icons": false, + "catalog.headers": false, "chat.input.maxlength": 100, "chat.styles.disabled": [], "chat.styles": [ diff --git a/src/api/GetUIVersion.ts b/src/api/GetUIVersion.ts index 881eea94..cf8d5a5d 100644 --- a/src/api/GetUIVersion.ts +++ b/src/api/GetUIVersion.ts @@ -1 +1 @@ -export const GetUIVersion = () => '2.1.0'; +export const GetUIVersion = () => '2.1.1'; diff --git a/src/api/utils/ConvertSeconds.ts b/src/api/utils/ConvertSeconds.ts new file mode 100644 index 00000000..f559dea9 --- /dev/null +++ b/src/api/utils/ConvertSeconds.ts @@ -0,0 +1,9 @@ +export const ConvertSeconds = (seconds: number) => +{ + let numDays = Math.floor(seconds / 86400); + let numHours = Math.floor((seconds % 86400) / 3600); + let numMinutes = Math.floor(((seconds % 86400) % 3600) / 60); + let numSeconds = ((seconds % 86400) % 3600) % 60; + + return numDays.toString().padStart(2, '0') + ':' + numHours.toString().padStart(2, '0') + ':' + numMinutes.toString().padStart(2, '0') + ':' + numSeconds.toString().padStart(2, '0'); +} diff --git a/src/api/utils/GetLocalStorage.ts b/src/api/utils/GetLocalStorage.ts new file mode 100644 index 00000000..e8ba878d --- /dev/null +++ b/src/api/utils/GetLocalStorage.ts @@ -0,0 +1 @@ +export const GetLocalStorage = (key: string) => JSON.parse(window.localStorage.getItem(key)) as T ?? null; diff --git a/src/api/utils/SetLocalStorage.ts b/src/api/utils/SetLocalStorage.ts new file mode 100644 index 00000000..02aa8f3d --- /dev/null +++ b/src/api/utils/SetLocalStorage.ts @@ -0,0 +1 @@ +export const SetLocalStorage = (key: string, value: T) => window.localStorage.setItem(key, JSON.stringify(value)); diff --git a/src/api/utils/WindowSaveOptions.ts b/src/api/utils/WindowSaveOptions.ts new file mode 100644 index 00000000..9aa84563 --- /dev/null +++ b/src/api/utils/WindowSaveOptions.ts @@ -0,0 +1,5 @@ +export interface WindowSaveOptions +{ + offset: { x: number, y: number }; + size: { width: number, height: number }; +} diff --git a/src/api/utils/index.ts b/src/api/utils/index.ts index eeb74fce..0c51fc49 100644 --- a/src/api/utils/index.ts +++ b/src/api/utils/index.ts @@ -1,5 +1,7 @@ export * from './CloneObject'; export * from './ColorUtils'; +export * from './ConvertSeconds'; +export * from './GetLocalStorage'; export * from './LocalizeBadgeDescription'; export * from './LocalizeBageName'; export * from './LocalizeFormattedNumber'; @@ -10,4 +12,6 @@ export * from './PlaySound'; export * from './ProductImageUtility'; export * from './Randomizer'; export * from './RoomChatFormatter'; +export * from './SetLocalStorage'; export * from './SoundNames'; +export * from './WindowSaveOptions'; diff --git a/src/assets/images/infostand/countown-timer.png b/src/assets/images/infostand/countown-timer.png new file mode 100644 index 00000000..ebfe6291 Binary files /dev/null and b/src/assets/images/infostand/countown-timer.png differ 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/card/NitroCardView.tsx b/src/common/card/NitroCardView.tsx index 329d3615..236f615f 100644 --- a/src/common/card/NitroCardView.tsx +++ b/src/common/card/NitroCardView.tsx @@ -1,5 +1,6 @@ -import { FC, useMemo } from 'react'; +import { FC, useEffect, useMemo, useRef } from 'react'; import { Column, ColumnProps } from '..'; +import { GetLocalStorage, SetLocalStorage, WindowSaveOptions } from '../../api'; import { DraggableWindow, DraggableWindowPosition, DraggableWindowProps } from '../draggable-window'; import { NitroCardContextProvider } from './NitroCardContext'; @@ -11,6 +12,7 @@ export interface NitroCardViewProps extends DraggableWindowProps, ColumnProps export const NitroCardView: FC = props => { const { theme = 'primary', uniqueKey = null, handleSelector = '.drag-handler', windowPosition = DraggableWindowPosition.CENTER, disableDrag = false, overflow = 'hidden', position = 'relative', gap = 0, classNames = [], ...rest } = props; + const elementRef = useRef(); const getClassNames = useMemo(() => { @@ -23,10 +25,40 @@ export const NitroCardView: FC = props => return newClassNames; }, [ theme, classNames ]); + useEffect(() => + { + if(!uniqueKey || !elementRef || !elementRef.current) return; + + const localStorage = GetLocalStorage(`nitro.windows.${ uniqueKey }`); + const element = elementRef.current; + + if(localStorage && localStorage.size) + { + element.style.width = `${ localStorage.size.width }px`; + element.style.height = `${ localStorage.size.height }px`; + } + + const observer = new ResizeObserver(event => + { + const newStorage = { ...GetLocalStorage>(`nitro.windows.${ uniqueKey }`) } as WindowSaveOptions; + + newStorage.size = { width: element.offsetWidth, height: element.offsetHeight }; + + SetLocalStorage(`nitro.windows.${ uniqueKey }`, newStorage); + }); + + observer.observe(element); + + return () => + { + observer.disconnect(); + } + }, [ uniqueKey ]); + return ( - + ); diff --git a/src/common/draggable-window/DraggableWindow.tsx b/src/common/draggable-window/DraggableWindow.tsx index 05813e42..286d51d1 100644 --- a/src/common/draggable-window/DraggableWindow.tsx +++ b/src/common/draggable-window/DraggableWindow.tsx @@ -2,10 +2,10 @@ import { MouseEventType, TouchEventType } from '@nitrots/nitro-renderer'; import { CSSProperties, FC, Key, MouseEvent as ReactMouseEvent, ReactNode, TouchEvent as ReactTouchEvent, useCallback, useEffect, useRef, useState } from 'react'; import { createPortal } from 'react-dom'; import { Base } from '..'; +import { GetLocalStorage, SetLocalStorage, WindowSaveOptions } from '../../api'; import { DraggableWindowPosition } from './DraggableWindowPosition'; const CURRENT_WINDOWS: HTMLElement[] = []; -const POS_MEMORY: Map = new Map(); const BOUNDS_THRESHOLD_TOP: number = 0; const BOUNDS_THRESHOLD_LEFT: number = 0; @@ -138,7 +138,14 @@ export const DraggableWindow: FC = props => setOffset({ x: offsetX, y: offsetY }); setIsDragging(false); - if(uniqueKey !== null) POS_MEMORY.set(uniqueKey, { x: offsetX, y: offsetY }); + if(uniqueKey !== null) + { + const newStorage = { ...GetLocalStorage(`nitro.windows.${ uniqueKey }`) } as WindowSaveOptions; + + newStorage.offset = { x: offsetX, y: offsetY }; + + SetLocalStorage(`nitro.windows.${ uniqueKey }`, newStorage); + } }, [ dragHandler, delta, offset, uniqueKey ]); const onDragMouseUp = useCallback((event: MouseEvent) => @@ -187,17 +194,6 @@ export const DraggableWindow: FC = props => break; } - if(uniqueKey !== null) - { - const memory = POS_MEMORY.get(uniqueKey); - - if(memory) - { - offsetX = memory.x; - offsetY = memory.y; - } - } - setDelta({ x: 0, y: 0 }); setOffset({ x: offsetX, y: offsetY }); @@ -253,6 +249,18 @@ export const DraggableWindow: FC = props => } }, [ isDragging, onDragMouseUp, onDragMouseMove, onDragTouchUp, onDragTouchMove ]); + useEffect(() => + { + if(!uniqueKey) return; + + const localStorage = GetLocalStorage(`nitro.windows.${ uniqueKey }`); + + if(!localStorage || !localStorage.offset) return; + + setDelta({ x: 0, y: 0 }); + if(localStorage.offset) setOffset(localStorage.offset); + }, [ uniqueKey ]); + return ( createPortal( diff --git a/src/common/index.scss b/src/common/index.scss index 3a08bf3c..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); } @@ -299,6 +301,19 @@ } } +.nitro-counter-time { + width: 36px; + height: 28px; + background: url("../assets/images/infostand/countown-timer.png"); + + div { + line-height: 28px; + text-align: center; + color: $white; + font-weight: bold; + } +} + .avatar-image { position: relative; width: 90px; @@ -375,15 +390,13 @@ content: ""; width: 100%; height: 100%; - background: url("../assets/images/unique/grid-bg-glass.png") center - no-repeat; + background: url("../assets/images/unique/grid-bg-glass.png") center no-repeat; bottom: 0; z-index: 4; } &.sold-out:after { - background: url("../assets/images/unique/grid-bg-sold-out.png") center - no-repeat, + background: url("../assets/images/unique/grid-bg-sold-out.png") center no-repeat, url("../assets/images/unique/grid-bg-glass.png") center no-repeat; } @@ -395,8 +408,7 @@ bottom: 1px; width: 100%; height: 9px; - background: url("../assets/images/unique/grid-count-bg.png") center - no-repeat; + background: url("../assets/images/unique/grid-count-bg.png") center no-repeat; z-index: 3; } } @@ -438,8 +450,7 @@ .unique-complete-plate { width: 170px; height: 29px; - background: url("../assets/images/unique/catalog-info-amount-bg.png") - no-repeat center; + background: url("../assets/images/unique/catalog-info-amount-bg.png") no-repeat center; z-index: 1; padding-top: 3px; @@ -535,12 +546,10 @@ z-index: 1; transition: all 1s; border-radius: calc(#{$border-radius} / 2); - background: repeating-linear-gradient( - $tertiary, - $tertiary 50%, - $quaternary 50%, - $quaternary 100% - ); + background: repeating-linear-gradient($tertiary, + $tertiary 50%, + $quaternary 50%, + $quaternary 100%); } .nitro-progress-bar-text { diff --git a/src/common/layout/LayoutCounterTimeView.tsx b/src/common/layout/LayoutCounterTimeView.tsx new file mode 100644 index 00000000..7460b054 --- /dev/null +++ b/src/common/layout/LayoutCounterTimeView.tsx @@ -0,0 +1,43 @@ +import { FC, useMemo } from 'react'; +import { LocalizeText } from '../../api'; +import { Base, BaseProps } from '../Base'; +import { Flex } from '../Flex'; + +interface LayoutCounterTimeViewProps extends BaseProps +{ + day: string; + hour: string; + minutes: string; + seconds: string; +} + +export const LayoutCounterTimeView: FC = props => +{ + const { day = '00', hour = '00', minutes = '00', seconds = '00', classNames = [], children = null, ...rest } = props; + + const getClassNames = useMemo(() => + { + const newClassNames: string[] = [ 'nitro-counter-time' ]; + + if(classNames.length) newClassNames.push(...classNames); + + return newClassNames; + }, [ classNames ]); + + return ( + + +
{ day != '00' ? day : hour }{ day != '00' ? LocalizeText('countdown_clock_unit_days') : LocalizeText('countdown_clock_unit_hours') }
+ + : + +
{ minutes }{ LocalizeText('countdown_clock_unit_minutes') }
+ + : + +
{ seconds }{ LocalizeText('countdown_clock_unit_seconds') }
+ + { children } +
+ ); +} diff --git a/src/common/layout/LayoutPetImageView.tsx b/src/common/layout/LayoutPetImageView.tsx index 6d4677be..e504fce2 100644 --- a/src/common/layout/LayoutPetImageView.tsx +++ b/src/common/layout/LayoutPetImageView.tsx @@ -20,6 +20,8 @@ export const LayoutPetImageView: FC = props => { const { figure = '', typeId = -1, paletteId = -1, petColor = 0xFFFFFF, customParts = [], posture = 'std', headOnly = false, direction = 0, scale = 1, style = {}, ...rest } = props; const [ petUrl, setPetUrl ] = useState(null); + const [ width, setWidth ] = useState(0); + const [ height, setHeight ] = useState(0); const isDisposed = useRef(false); const getStyle = useMemo(() => @@ -35,10 +37,13 @@ export const LayoutPetImageView: FC = props => if(!(scale % 1)) newStyle.imageRendering = 'pixelated'; } + newStyle.width = width; + newStyle.height = height; + if(Object.keys(style).length) newStyle = { ...newStyle, ...style }; return newStyle; - }, [ petUrl, scale, style ]); + }, [ petUrl, scale, style, width, height ]); useEffect(() => { @@ -67,8 +72,19 @@ export const LayoutPetImageView: FC = props => { if(isDisposed.current) return; - if(image) setPetUrl(image.src); - else if(texture) setPetUrl(TextureUtils.generateImageUrl(texture)); + if(image) + { + setPetUrl(image.src); + setWidth(image.width); + setHeight(image.height); + } + + else if(texture) + { + setPetUrl(TextureUtils.generateImageUrl(texture)); + setWidth(texture.width); + setHeight(texture.height); + } }, imageFailed: (id) => { @@ -80,9 +96,12 @@ export const LayoutPetImageView: FC = props => { const image = imageResult.getImage(); - if(image) setPetUrl(image.src); - - + if(image) + { + setPetUrl(image.src); + setWidth(image.width); + setHeight(image.height); + } } }, [ figure, typeId, paletteId, petColor, customParts, posture, headOnly, direction ]); diff --git a/src/common/layout/index.ts b/src/common/layout/index.ts index a7bdd15c..3c4238e1 100644 --- a/src/common/layout/index.ts +++ b/src/common/layout/index.ts @@ -1,6 +1,7 @@ export * from './LayoutAvatarImageView'; export * from './LayoutBackgroundImage'; export * from './LayoutBadgeImageView'; +export * from './LayoutCounterTimeView'; export * from './LayoutCurrencyIcon'; export * from './LayoutFurniIconImageView'; export * from './LayoutFurniImageView'; 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/CatalogView.scss b/src/components/catalog/CatalogView.scss index 4101cd13..9e5fe9d7 100644 --- a/src/components/catalog/CatalogView.scss +++ b/src/components/catalog/CatalogView.scss @@ -2,7 +2,7 @@ width: $catalog-width; height: $catalog-height; - font[size='16'] { + font[size="16"] { font-size: 20px; } @@ -22,7 +22,7 @@ .nitro-catalog-gift { width: 325px; - + .gift-preview { width: 80px; height: 80px; @@ -37,15 +37,24 @@ } .nitro-catalog-navigation-grid-container { - border-color: #B6BEC5 !important; - background-color: #CDD3D9; + border-color: #b6bec5 !important; + background-color: #cdd3d9; border: 2px solid; + .nitro-catalog-navigation-section { + display: grid; + + .nitro-catalog-navigation-section { + padding-left: 5px; + border-left: 2px solid #b6bec5; + } + } + .layout-grid-item { font-size: $font-size-sm; height: 23px !important; border-color: unset !important; - background-color: #CDD3D9; + background-color: #cdd3d9; border: 0 !important; padding: 1px 3px; @@ -58,23 +67,21 @@ } .nitro-catalog-layout-info-loyalty { - .info-loyalty-content { background-repeat: no-repeat; background-position: top right; - background-image: url('../../assets/images/catalog/diamond_info_illustration.gif'); - padding-right:123px; + background-image: url("../../assets/images/catalog/diamond_info_illustration.gif"); + padding-right: 123px; } .info-image { width: 123px; - height:350px; - background-image: url('../../assets/images/catalog/diamond_info_illustration.gif'); + height: 350px; + background-image: url("../../assets/images/catalog/diamond_info_illustration.gif"); } } .nitro-catalog-layout-vip-buy-grid { - .layout-grid-item { height: 50px !important; max-height: 50px !important; @@ -82,20 +89,18 @@ .icon-hc-banner { width: 68px; height: 40px; - background: url('../../assets/images/catalog/hc_big.png') center no-repeat; + background: url("../../assets/images/catalog/hc_big.png") center no-repeat; } } } .nitro-catalog-layout-marketplace-grid { - .layout-grid-item { height: 75px !important; } } .nitro-catalog-layout-vip-gifts-grid { - .layout-grid-item { height: 55px !important; max-height: 55px !important; @@ -108,8 +113,12 @@ } .nitro-catalog-layout-bundle-grid { - .layout-grid-item { background-color: transparent; } } + +.nitro-catalog-header { + width: 290px; + height: 60px; +} diff --git a/src/components/catalog/CatalogView.tsx b/src/components/catalog/CatalogView.tsx index 3af9b189..f15fe8d2 100644 --- a/src/components/catalog/CatalogView.tsx +++ b/src/components/catalog/CatalogView.tsx @@ -1,8 +1,9 @@ import { ILinkEventTracker } from '@nitrots/nitro-renderer'; import { FC, useEffect } from 'react'; -import { AddEventLinkTracker, LocalizeText, RemoveLinkEventTracker } from '../../api'; -import { Column, Grid, NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView } from '../../common'; +import { AddEventLinkTracker, GetConfiguration, LocalizeText, RemoveLinkEventTracker } from '../../api'; +import { Column, Flex, Grid, NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView } from '../../common'; import { useCatalog } from '../../hooks'; +import { CatalogIconView } from './views/catalog-icon/CatalogIconView'; import { CatalogGiftView } from './views/gift/CatalogGiftView'; import { CatalogNavigationView } from './views/navigation/CatalogNavigationView'; import { GetCatalogLayout } from './views/page/layout/GetCatalogLayout'; @@ -10,7 +11,7 @@ import { MarketplacePostOfferView } from './views/page/layout/marketplace/Market export const CatalogView: FC<{}> = props => { - const { isVisible = false, setIsVisible = null, rootNode = null, currentPage = null, navigationHidden = false, setNavigationHidden = null, activeNodes = [], searchResult = null, setSearchResult = null, openPageByName = null, openPageByOfferId = null, activateNode = null } = useCatalog(); + const { isVisible = false, setIsVisible = null, rootNode = null, currentPage = null, navigationHidden = false, setNavigationHidden = null, activeNodes = [], searchResult = null, setSearchResult = null, openPageByName = null, openPageByOfferId = null, activateNode = null, getNodeById } = useCatalog(); useEffect(() => { @@ -68,7 +69,7 @@ export const CatalogView: FC<{}> = props => return ( <> { isVisible && - + setIsVisible(false) } /> { rootNode && (rootNode.children.length > 0) && rootNode.children.map(child => @@ -81,8 +82,11 @@ export const CatalogView: FC<{}> = props => if(searchResult) setSearchResult(null); activateNode(child); - } }> - { child.localization } + } } > + + { GetConfiguration('catalog.tab.icons') && } + { child.localization } + ); }) } diff --git a/src/components/catalog/views/catalog-header/CatalogHeaderView.tsx b/src/components/catalog/views/catalog-header/CatalogHeaderView.tsx new file mode 100644 index 00000000..eb8d92ce --- /dev/null +++ b/src/components/catalog/views/catalog-header/CatalogHeaderView.tsx @@ -0,0 +1,26 @@ +import { FC, useEffect, useState } from 'react'; +import { GetConfiguration } from '../../../../api'; +import { Flex } from '../../../../common'; + +export interface CatalogHeaderViewProps +{ + imageUrl?: string; +} + +export const CatalogHeaderView: FC = props => +{ + const { imageUrl = null } = props; + const [ displayImageUrl, setDisplayImageUrl ] = useState(''); + + useEffect(() => + { + setDisplayImageUrl(imageUrl ?? GetConfiguration('catalog.asset.image.url').replace('%name%', 'catalog_header_roombuilder')); + }, [ imageUrl ]); + + return + + { + currentTarget.src = GetConfiguration('catalog.asset.image.url').replace('%name%', 'catalog_header_roombuilder'); + } } /> + ; +} diff --git a/src/components/catalog/views/navigation/CatalogNavigationItemView.tsx b/src/components/catalog/views/navigation/CatalogNavigationItemView.tsx index 5eb1c45c..9179edba 100644 --- a/src/components/catalog/views/navigation/CatalogNavigationItemView.tsx +++ b/src/components/catalog/views/navigation/CatalogNavigationItemView.tsx @@ -1,7 +1,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FC } from 'react'; import { ICatalogNode } from '../../../../api'; -import { LayoutGridItem, Text } from '../../../../common'; +import { Base, LayoutGridItem, Text } from '../../../../common'; import { useCatalog } from '../../../../hooks'; import { CatalogIconView } from '../catalog-icon/CatalogIconView'; import { CatalogNavigationSetView } from './CatalogNavigationSetView'; @@ -9,23 +9,24 @@ import { CatalogNavigationSetView } from './CatalogNavigationSetView'; export interface CatalogNavigationItemViewProps { node: ICatalogNode; + child?: boolean; } export const CatalogNavigationItemView: FC = props => { - const { node = null } = props; + const { node = null, child = false } = props; const { activateNode = null } = useCatalog(); return ( - <> - activateNode(node) }> + + activateNode(node) } className={ child ? 'inset' : '' }> { node.localization } { node.isBranch && } { node.isOpen && node.isBranch && - } - + } + ); } diff --git a/src/components/catalog/views/navigation/CatalogNavigationSetView.tsx b/src/components/catalog/views/navigation/CatalogNavigationSetView.tsx index 19f71f47..8bfdd480 100644 --- a/src/components/catalog/views/navigation/CatalogNavigationSetView.tsx +++ b/src/components/catalog/views/navigation/CatalogNavigationSetView.tsx @@ -5,11 +5,12 @@ import { CatalogNavigationItemView } from './CatalogNavigationItemView'; export interface CatalogNavigationSetViewProps { node: ICatalogNode; + child?: boolean; } export const CatalogNavigationSetView: FC = props => { - const { node = null } = props; + const { node = null, child = false } = props; return ( <> @@ -17,7 +18,7 @@ export const CatalogNavigationSetView: FC = props { if(!n.isVisible) return null; - return + return }) } ); diff --git a/src/components/catalog/views/navigation/CatalogNavigationView.tsx b/src/components/catalog/views/navigation/CatalogNavigationView.tsx index 3924928d..2d03f06d 100644 --- a/src/components/catalog/views/navigation/CatalogNavigationView.tsx +++ b/src/components/catalog/views/navigation/CatalogNavigationView.tsx @@ -20,7 +20,7 @@ export const CatalogNavigationView: FC = props => <> - + { searchResult && (searchResult.filteredNodes.length > 0) && searchResult.filteredNodes.map((n, index) => { return ; diff --git a/src/components/catalog/views/page/layout/CatalogLayoutDefaultView.tsx b/src/components/catalog/views/page/layout/CatalogLayoutDefaultView.tsx index 262685ae..79979a53 100644 --- a/src/components/catalog/views/page/layout/CatalogLayoutDefaultView.tsx +++ b/src/components/catalog/views/page/layout/CatalogLayoutDefaultView.tsx @@ -1,7 +1,8 @@ import { FC } from 'react'; -import { ProductTypeEnum } from '../../../../../api'; -import { Column, Flex, Grid, Text } from '../../../../../common'; +import { GetConfiguration, ProductTypeEnum } from '../../../../../api'; +import { Column, Flex, Grid, LayoutImage, Text } from '../../../../../common'; import { useCatalog } from '../../../../../hooks'; +import { CatalogHeaderView } from '../../catalog-header/CatalogHeaderView'; import { CatalogAddOnBadgeWidgetView } from '../widgets/CatalogAddOnBadgeWidgetView'; import { CatalogItemGridWidgetView } from '../widgets/CatalogItemGridWidgetView'; import { CatalogLimitedItemWidgetView } from '../widgets/CatalogLimitedItemWidgetView'; @@ -14,42 +15,47 @@ import { CatalogLayoutProps } from './CatalogLayout.types'; export const CatalogLayoutDefaultView: FC = props => { const { page = null } = props; - const { currentOffer = null } = useCatalog(); + const { currentOffer = null, currentPage = null } = useCatalog(); return ( - - - - - - { !currentOffer && - <> - { !!page.localization.getImage(1) && } - - } - { currentOffer && - <> - - { (currentOffer.product.productType !== ProductTypeEnum.BADGE) && - <> - - - - } - { (currentOffer.product.productType === ProductTypeEnum.BADGE) && } - - - { currentOffer.localizationName } - - - - - + <> + + + { GetConfiguration('catalog.headers') && + } + + + + { !currentOffer && + <> + { !!page.localization.getImage(1) && + } + + } + { currentOffer && + <> + + { (currentOffer.product.productType !== ProductTypeEnum.BADGE) && + <> + + + + } + { (currentOffer.product.productType === ProductTypeEnum.BADGE) && } - - - } - - + + { currentOffer.localizationName } + + + + + + + + + } + + + ); } diff --git a/src/components/catalog/views/page/layout/marketplace/MarketplacePostOfferView.tsx b/src/components/catalog/views/page/layout/marketplace/MarketplacePostOfferView.tsx index f9f614d5..9c649fe0 100644 --- a/src/components/catalog/views/page/layout/marketplace/MarketplacePostOfferView.tsx +++ b/src/components/catalog/views/page/layout/marketplace/MarketplacePostOfferView.tsx @@ -9,10 +9,22 @@ export const MarketplacePostOfferView : FC<{}> = props => { const [ item, setItem ] = useState(null); const [ askingPrice, setAskingPrice ] = useState(0); + const [ tempAskingPrice, setTempAskingPrice ] = useState('0'); const { catalogOptions = null, setCatalogOptions = null } = useCatalog(); const { marketplaceConfiguration = null } = catalogOptions; const { showConfirm = null } = useNotification(); + const updateAskingPrice = (price: string) => + { + setTempAskingPrice(price); + + const newValue = parseInt(price); + + if(isNaN(newValue) || (newValue === askingPrice)) return; + + setAskingPrice(parseInt(price)); + } + useMessageEvent(MarketplaceConfigurationEvent, event => { const parser = event.getParser(); @@ -64,7 +76,7 @@ export const MarketplacePostOfferView : FC<{}> = props => setItem(null) }, null, null, LocalizeText('inventory.marketplace.confirm_offer.title')); } - + return ( setItem(null) } /> @@ -83,7 +95,7 @@ export const MarketplacePostOfferView : FC<{}> = props => { LocalizeText('inventory.marketplace.make_offer.expiration_info', [ 'time' ], [ marketplaceConfiguration.offerTime.toString() ]) }
- setAskingPrice(parseInt(event.target.value)) } placeholder={ LocalizeText('inventory.marketplace.make_offer.price_request') } /> + updateAskingPrice(event.target.value) } placeholder={ LocalizeText('inventory.marketplace.make_offer.price_request') } /> { ((askingPrice < marketplaceConfiguration.minimumPrice) || isNaN(askingPrice)) && { LocalizeText('inventory.marketplace.make_offer.min_price', [ 'minprice' ], [ marketplaceConfiguration.minimumPrice.toString() ]) } diff --git a/src/components/catalog/views/page/widgets/CatalogBundleGridWidgetView.tsx b/src/components/catalog/views/page/widgets/CatalogBundleGridWidgetView.tsx index d98ff1dd..84039c93 100644 --- a/src/components/catalog/views/page/widgets/CatalogBundleGridWidgetView.tsx +++ b/src/components/catalog/views/page/widgets/CatalogBundleGridWidgetView.tsx @@ -1,4 +1,4 @@ -import { FC } from 'react'; +import { FC, useEffect, useRef } from 'react'; import { AutoGrid, AutoGridProps, LayoutGridItem } from '../../../../../common'; import { useCatalog } from '../../../../../hooks'; @@ -11,11 +11,17 @@ export const CatalogBundleGridWidgetView: FC = { const { columnCount = 5, children = null, ...rest } = props; const { currentOffer = null } = useCatalog(); + const elementRef = useRef(); + + useEffect(() => + { + if(elementRef && elementRef.current) elementRef.current.scrollTop = 0; + }, [ currentOffer ]); if(!currentOffer) return null; return ( - + { currentOffer.products && (currentOffer.products.length > 0) && currentOffer.products.map((product, index) => ) } { children } diff --git a/src/components/catalog/views/page/widgets/CatalogItemGridWidgetView.tsx b/src/components/catalog/views/page/widgets/CatalogItemGridWidgetView.tsx index 933dede6..e4660545 100644 --- a/src/components/catalog/views/page/widgets/CatalogItemGridWidgetView.tsx +++ b/src/components/catalog/views/page/widgets/CatalogItemGridWidgetView.tsx @@ -1,4 +1,4 @@ -import { FC } from 'react'; +import { FC, useEffect, useRef } from 'react'; import { IPurchasableOffer, ProductTypeEnum } from '../../../../../api'; import { AutoGrid, AutoGridProps } from '../../../../../common'; import { useCatalog } from '../../../../../hooks'; @@ -13,6 +13,12 @@ export const CatalogItemGridWidgetView: FC = pro { const { columnCount = 5, children = null, ...rest } = props; const { currentOffer = null, setCurrentOffer = null, currentPage = null, setPurchaseOptions = null } = useCatalog(); + const elementRef = useRef(); + + useEffect(() => + { + if(elementRef && elementRef.current) elementRef.current.scrollTop = 0; + }, [ currentPage ]); if(!currentPage) return null; @@ -38,7 +44,7 @@ export const CatalogItemGridWidgetView: FC = pro } return ( - + { currentPage.offers && (currentPage.offers.length > 0) && currentPage.offers.map((offer, index) => ) } { children } 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/catalog/views/page/widgets/CatalogSpacesWidgetView.tsx b/src/components/catalog/views/page/widgets/CatalogSpacesWidgetView.tsx index fdc3f5f4..3ab901e6 100644 --- a/src/components/catalog/views/page/widgets/CatalogSpacesWidgetView.tsx +++ b/src/components/catalog/views/page/widgets/CatalogSpacesWidgetView.tsx @@ -1,4 +1,4 @@ -import { FC, useEffect, useState } from 'react'; +import { FC, useEffect, useRef, useState } from 'react'; import { IPurchasableOffer, LocalizeText, Offer, ProductTypeEnum } from '../../../../../api'; import { AutoGrid, AutoGridProps, Button, ButtonGroup } from '../../../../../common'; import { useCatalog } from '../../../../../hooks'; @@ -18,6 +18,7 @@ export const CatalogSpacesWidgetView: FC = props = const [ selectedGroupIndex, setSelectedGroupIndex ] = useState(-1); const [ selectedOfferForGroup, setSelectedOfferForGroup ] = useState(null); const { currentPage = null, currentOffer = null, setCurrentOffer = null, setPurchaseOptions = null } = useCatalog(); + const elementRef = useRef(); const setSelectedOffer = (offer: IPurchasableOffer) => { @@ -91,6 +92,11 @@ export const CatalogSpacesWidgetView: FC = props = }); }, [ currentOffer, selectedGroupIndex, selectedOfferForGroup, setPurchaseOptions ]); + useEffect(() => + { + if(elementRef && elementRef.current) elementRef.current.scrollTop = 0; + }, [ selectedGroupIndex ]); + if(!groupedOffers || (selectedGroupIndex === -1)) return null; const offers = groupedOffers[selectedGroupIndex]; @@ -100,7 +106,7 @@ export const CatalogSpacesWidgetView: FC = props = { SPACES_GROUP_NAMES.map((name, index) => ) } - + { offers && (offers.length > 0) && offers.map((offer, index) => setSelectedOffer(offer) } />) } { children } 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/navigator/NavigatorView.tsx b/src/components/navigator/NavigatorView.tsx index 9b3ae5eb..dcc2f9eb 100644 --- a/src/components/navigator/NavigatorView.tsx +++ b/src/components/navigator/NavigatorView.tsx @@ -24,6 +24,7 @@ export const NavigatorView: FC<{}> = props => const [ needsSearch, setNeedsSearch ] = useState(false); const { searchResult = null, topLevelContext = null, topLevelContexts = null, navigatorData = null } = useNavigator(); const pendingSearch = useRef<{ value: string, code: string }>(null); + const elementRef = useRef(); useRoomSessionManagerEvent(RoomSessionEvent.CREATED, event => { @@ -158,6 +159,8 @@ export const NavigatorView: FC<{}> = props => if(!searchResult) return; setIsLoading(false); + + if(elementRef && elementRef.current) elementRef.current.scrollTop = 0; }, [ searchResult ]); useEffect(() => @@ -214,7 +217,7 @@ export const NavigatorView: FC<{}> = props => { !isCreatorOpen && <> - + { (searchResult && searchResult.results.map((result, index) => )) } } diff --git a/src/components/navigator/views/search/NavigatorSearchResultView.tsx b/src/components/navigator/views/search/NavigatorSearchResultView.tsx index 143c3435..a85daaca 100644 --- a/src/components/navigator/views/search/NavigatorSearchResultView.tsx +++ b/src/components/navigator/views/search/NavigatorSearchResultView.tsx @@ -13,7 +13,6 @@ export interface NavigatorSearchResultViewProps extends AutoGridProps export const NavigatorSearchResultView: FC = props => { const { searchResult = null, ...rest } = props; - const [ isExtended, setIsExtended ] = useState(true); const [ displayMode, setDisplayMode ] = useState(0); @@ -44,7 +43,7 @@ export const NavigatorSearchResultView: FC = pro //setIsExtended(searchResult.closed); setDisplayMode(searchResult.mode); - }, [ searchResult,props ]); + }, [ searchResult ]); const gridHasTwoColumns = (displayMode >= NavigatorSearchResultViewDisplayMode.THUMBNAILS); diff --git a/src/components/room/widgets/avatar-info/AvatarInfoWidgetView.scss b/src/components/room/widgets/avatar-info/AvatarInfoWidgetView.scss index 7edd0c7b..558b0297 100644 --- a/src/components/room/widgets/avatar-info/AvatarInfoWidgetView.scss +++ b/src/components/room/widgets/avatar-info/AvatarInfoWidgetView.scss @@ -70,6 +70,17 @@ } } + .body-image-plant { + display: flex; + align-items: center; + justify-content: center; + width: 100%; + max-width: 68px; + height: 85px; + max-height: 90px; + border-radius: $border-radius; + } + .badge-image { width: 45px; height: 45px; diff --git a/src/components/room/widgets/avatar-info/infostand/InfoStandWidgetPetView.tsx b/src/components/room/widgets/avatar-info/infostand/InfoStandWidgetPetView.tsx index 7847f439..a590b785 100644 --- a/src/components/room/widgets/avatar-info/infostand/InfoStandWidgetPetView.tsx +++ b/src/components/room/widgets/avatar-info/infostand/InfoStandWidgetPetView.tsx @@ -1,7 +1,9 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { FC } from 'react'; -import { AvatarInfoPet, LocalizeText } from '../../../../../api'; -import { Base, Column, Flex, LayoutPetImageView, Text, UserProfileIconView } from '../../../../../common'; +import { PetRespectComposer, PetType } from '@nitrots/nitro-renderer'; +import { FC, useEffect, useState } from 'react'; +import { AvatarInfoPet, ConvertSeconds, CreateLinkEvent, GetConfiguration, LocalizeText, SendMessageComposer } from '../../../../../api'; +import { Base, Button, Column, Flex, LayoutCounterTimeView, LayoutPetImageView, LayoutRarityLevelView, Text, UserProfileIconView } from '../../../../../common'; +import { useRoom, useSessionInfo } from '../../../../../hooks'; interface InfoStandWidgetPetViewProps { @@ -12,72 +14,196 @@ interface InfoStandWidgetPetViewProps export const InfoStandWidgetPetView: FC = props => { const { avatarInfo = null, onClose = null } = props; + const [ remainingGrowTime, setRemainingGrowTime ] = useState(0); + const [ remainingTimeToLive, setRemainingTimeToLive ] = useState(0); + const { roomSession = null } = useRoom(); + const { petRespectRemaining = 0, respectPet = null } = useSessionInfo(); + + useEffect(() => + { + setRemainingGrowTime(avatarInfo.remainingGrowTime); + setRemainingTimeToLive(avatarInfo.remainingTimeToLive); + }, [ avatarInfo ]); + + useEffect(() => + { + if((avatarInfo.petType !== PetType.MONSTERPLANT) || avatarInfo.dead) return; + + const interval = setInterval(() => + { + setRemainingGrowTime(prevValue => (prevValue - 1)); + setRemainingTimeToLive(prevValue => (prevValue - 1)); + }, 1000); + + return () => clearInterval(interval); + }, [ avatarInfo ]); if(!avatarInfo) return null; + const processButtonAction = (action: string) => + { + let hideMenu = true; + + if (!action || action == '') return; + + switch (action) + { + case 'respect': + respectPet(avatarInfo.id); + + if((petRespectRemaining - 1) >= 1) hideMenu = false; + break; + case 'buyfood': + CreateLinkEvent('catalog/open/' + GetConfiguration('catalog.links')['pets.buy_saddle']); + break; + case 'train': + // not coded + break; + case 'treat': + SendMessageComposer(new PetRespectComposer(avatarInfo.id)); + break; + case 'compost': + roomSession?.compostPlant(avatarInfo.id); + break; + case 'pick_up': + roomSession?.pickupPet(avatarInfo.id); + break; + } + + if(hideMenu) onClose(); + } + return ( - - - - - { 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 } - - - + + + + + + { avatarInfo.name } + + + { LocalizeText(`pet.breed.${ avatarInfo.petType }.${ avatarInfo.petBreed }`) } +
+
+ { (avatarInfo.petType === PetType.MONSTERPLANT) && + <> + + +
- - { LocalizeText('infostand.pet.text.experience') } - - - { avatarInfo.experience + '/' + avatarInfo.levelExperienceGoal } - - - + + { !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] } + + + + + { remainingGrowTime != 0 && remainingGrowTime > 0 && + + { LocalizeText('infostand.pet.text.growth') } + + } + + { LocalizeText('infostand.pet.text.raritylevel', [ 'level' ], [ LocalizeText(`infostand.pet.raritylevel.${ avatarInfo.rarityLevel }`) ]) } + + +
- - { LocalizeText('infostand.pet.text.energy') } - - - { avatarInfo.energy + '/' + avatarInfo.maximumEnergy } - - - + + { LocalizeText('pet.age', [ 'age' ], [ avatarInfo.age.toString() ]) } +
-
-
-
-
- - { LocalizeText('infostand.text.petrespect', [ 'count' ], [ avatarInfo.respect.toString() ]) } - { LocalizeText('pet.age', [ 'age' ], [ avatarInfo.age.toString() ]) } -
-
- - - - - { LocalizeText('infostand.text.petowner', [ 'name' ], [ avatarInfo.ownerName ]) } - - + } + { (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 } + + + + + + +
+
+ + { (avatarInfo.petType !== PetType.MONSTERPLANT) && + { LocalizeText('infostand.text.petrespect', [ 'count' ], [ avatarInfo.respect.toString() ]) } } + { LocalizeText('pet.age', [ 'age' ], [ avatarInfo.age.toString() ]) } +
+
+ } + + + + + { LocalizeText('infostand.text.petowner', [ 'name' ], [ avatarInfo.ownerName ]) } + + +
+ + { (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/FurnitureGiftOpeningView.tsx b/src/components/room/widgets/furniture/FurnitureGiftOpeningView.tsx index d6a8c001..75ffe8ba 100644 --- a/src/components/room/widgets/furniture/FurnitureGiftOpeningView.tsx +++ b/src/components/room/widgets/furniture/FurnitureGiftOpeningView.tsx @@ -1,14 +1,24 @@ import { FC } from 'react'; -import { CreateLinkEvent, LocalizeText } from '../../../../api'; +import { attemptItemPlacement, CreateLinkEvent, LocalizeText } from '../../../../api'; import { Button, Column, Flex, LayoutGiftTagView, LayoutImage, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../../common'; -import { useFurniturePresentWidget } from '../../../../hooks'; +import { useFurniturePresentWidget, useInventoryFurni } from '../../../../hooks'; export const FurnitureGiftOpeningView: FC<{}> = props => { const { objectId = -1, classId = -1, itemType = null, text = null, isOwnerOfFurniture = false, senderName = null, senderFigure = null, placedItemId = -1, placedItemType = null, placedInRoom = false, imageUrl = null, openPresent = null, onClose = null } = useFurniturePresentWidget(); + const { groupItems = [] } = useInventoryFurni(); if(objectId === -1) return null; + const place = (itemId: number) => + { + const groupItem = groupItems.find(group => (group.getItemById(itemId)?.id === itemId)); + + if(groupItem) attemptItemPlacement(groupItem); + + onClose(); + } + return ( @@ -45,7 +55,7 @@ export const FurnitureGiftOpeningView: FC<{}> = props => } - 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) } />