mirror of
https://github.com/duckietm/Nitro-Cool-UI.git
synced 2025-06-21 14:26:58 +00:00
🆕 Added Build Tool to the UI
This commit is contained in:
parent
917912b909
commit
39dc37dda3
@ -2,8 +2,8 @@
|
||||
position: absolute;
|
||||
bottom: $toolbar-height + 25px;
|
||||
left: 0px;
|
||||
display: flex; // Lay out children side by side
|
||||
align-items: flex-start; // Align items to the top
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
|
||||
.nitro-room-tools {
|
||||
background: #212131;
|
||||
@ -48,11 +48,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
.nitro-room-tools-side-container {
|
||||
.nitro-room-tools-side-container {
|
||||
display: flex;
|
||||
flex-direction: column; // Stack children vertically
|
||||
margin-left: 10px; // Space between nitro-room-tools and side container
|
||||
gap: 10px; // Space between history and info
|
||||
flex-direction: column;
|
||||
margin-left: 10px;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.nitro-room-history {
|
||||
@ -61,7 +61,7 @@
|
||||
transition: all .2s ease;
|
||||
width: 150px;
|
||||
overflow: hidden;
|
||||
z-index: 1;
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
.nitro-room-tools-info {
|
||||
@ -170,6 +170,104 @@
|
||||
}
|
||||
}
|
||||
|
||||
.buildtool-box {
|
||||
background-color: 3D5D63;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #fff;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.buildtool-box-left {
|
||||
background-color: #3D5D63;
|
||||
}
|
||||
|
||||
.buildtool-box-right {
|
||||
background-color: #3D5D63;
|
||||
}
|
||||
|
||||
.buildtool-movefurni {
|
||||
background-color: #E55959;
|
||||
color: #fff;
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
border: 1px solid #fff;
|
||||
cursor: pointer;
|
||||
border-radius: 3px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
transition: filter 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
filter: brightness(1.5);
|
||||
}
|
||||
}
|
||||
|
||||
.buildtool-rotatefurni {
|
||||
background-color: #D1A245;
|
||||
color: #000;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border: 2px solid #eee;
|
||||
cursor: pointer;
|
||||
border-radius: 14px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
transition: filter 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
filter: brightness(1.5);
|
||||
}
|
||||
}
|
||||
|
||||
.buildtool-setheight {
|
||||
color: #fff;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border: 1px solid #fff;
|
||||
cursor: pointer;
|
||||
border-radius: 3px;
|
||||
line-height: 1.0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
transition: filter 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
filter: brightness(1.5);
|
||||
}
|
||||
}
|
||||
|
||||
.buildtool-setheightup {
|
||||
background-color: #247FD1;
|
||||
}
|
||||
|
||||
.buildtool-setheightdown {
|
||||
background-color: #44A750;
|
||||
}
|
||||
|
||||
.button-leftdown {
|
||||
transform: rotate(135deg);
|
||||
}
|
||||
|
||||
.button-leftup {
|
||||
transform: rotate(225deg);
|
||||
}
|
||||
|
||||
.button-rightdown {
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
.button-rightup {
|
||||
transform: rotate(315deg);
|
||||
}
|
||||
|
||||
.floor-spaceing {
|
||||
gap: 0.6em!important;
|
||||
}
|
||||
@import './avatar-info/AvatarInfoWidgetView';
|
||||
@import './chat/ChatWidgetView';
|
||||
@import './chat-input/ChatInputView';
|
||||
|
@ -1,5 +1,7 @@
|
||||
import { CrackableDataType, GroupInformationComposer, GroupInformationEvent, NowPlayingEvent, RoomControllerLevel, RoomObjectCategory, RoomObjectOperationType, RoomObjectVariable, RoomWidgetEnumItemExtradataParameter, RoomWidgetFurniInfoUsagePolicyEnum, SetObjectDataMessageComposer, SongInfoReceivedEvent, StringDataType } from '@nitrots/nitro-renderer';
|
||||
import { CrackableDataType, FurnitureFloorUpdateEvent, GroupInformationComposer, GroupInformationEvent, NowPlayingEvent, RoomControllerLevel, RoomObjectCategory, RoomObjectOperationType, RoomObjectVariable, RoomWidgetEnumItemExtradataParameter, RoomWidgetFurniInfoUsagePolicyEnum, SetObjectDataMessageComposer, SongInfoReceivedEvent, StringDataType, UpdateFurniturePositionComposer } from '@nitrots/nitro-renderer';
|
||||
import { FC, useCallback, useEffect, useState } from 'react';
|
||||
import { FaTimes } from 'react-icons/fa';
|
||||
import { GrFormNextLink, GrRotateLeft, GrRotateRight } from 'react-icons/gr';
|
||||
import { AvatarInfoFurni, CreateLinkEvent, GetGroupInformation, GetNitroInstance, GetRoomEngine, LocalizeText, SendMessageComposer } from '../../../../../api';
|
||||
import { Base, Button, Column, Flex, LayoutBadgeImageView, LayoutLimitedEditionCompactPlateView, LayoutRarityLevelView, Text, UserProfileIconView } from '../../../../../common';
|
||||
import { useMessageEvent, useRoom, useSoundEvent } from '../../../../../hooks';
|
||||
@ -40,6 +42,217 @@ export const InfoStandWidgetFurniView: FC<InfoStandWidgetFurniViewProps> = props
|
||||
const [ songCreator, setSongCreator ] = useState<string>('');
|
||||
const [itemLocation, setItemLocation] = useState<{ x: number; y: number; z: number; }>({ x: -1, y: -1, z: -1 });
|
||||
|
||||
const [ dropdownOpen, setDropdownOpen ] = useState(sessionStorage.getItem('dropdownOpen') === 'true');
|
||||
const [ furniLocationX, setFurniLocationX ] = useState(null);
|
||||
const [ furniLocationY, setFurniLocationY ] = useState(null);
|
||||
const [ furniLocationZ, setFurniLocationZ ] = useState(null);
|
||||
const [ furniDirection, setFurniDirection ] = useState(null);
|
||||
const [ furniState, setFurniState ] = useState(null);
|
||||
|
||||
const sendUpdate = useCallback((deltaX: number, deltaY: number, deltaZ: number = 0, deltaDirection: number = 0) =>
|
||||
{
|
||||
if (!avatarInfo) return;
|
||||
|
||||
const roomId = GetRoomEngine().activeRoomId;
|
||||
const roomObject = GetRoomEngine().getRoomObject(roomId, avatarInfo.id, avatarInfo.category);
|
||||
if (!roomObject) return;
|
||||
|
||||
const newX = roomObject.getLocation().x + deltaX;
|
||||
const newY = roomObject.getLocation().y + deltaY;
|
||||
const newZ = deltaZ * 10000;
|
||||
|
||||
const currentDirection = roomObject.getDirection().x;
|
||||
|
||||
const newDirection = (deltaDirection !== 0)
|
||||
? getValidRoomObjectDirection(roomObject, deltaDirection > 0) / 45
|
||||
: currentDirection / 45;
|
||||
|
||||
SendMessageComposer(new UpdateFurniturePositionComposer(avatarInfo.id, newX, newY, newZ, newDirection));
|
||||
}, [ avatarInfo ]);
|
||||
|
||||
function getRotationIndex(directionVector)
|
||||
{
|
||||
const angle = directionVector.x;
|
||||
|
||||
switch(angle)
|
||||
{
|
||||
case 0: return 0;
|
||||
case 45: return 1;
|
||||
case 90: return 2;
|
||||
case 135: return 3;
|
||||
case 180: return 4;
|
||||
case 225: return 5;
|
||||
case 270: return 6;
|
||||
case 315: return 7;
|
||||
default: return null; // Handle unexpected angles
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
const roomId = roomSession.roomId;
|
||||
const objectId = avatarInfo.id;
|
||||
const isWallItem = avatarInfo.isWallItem;
|
||||
|
||||
const locationString = GetRoomEngine().getFurniLocation(roomId, objectId, isWallItem);
|
||||
const locationVector = parseVector3d(locationString);
|
||||
|
||||
if (locationVector)
|
||||
{
|
||||
setFurniLocationX(locationVector.x);
|
||||
setFurniLocationY(locationVector.y);
|
||||
setFurniLocationZ(locationVector.z);
|
||||
}
|
||||
|
||||
const directionString = GetRoomEngine().getFurniDirection(roomId, objectId, isWallItem);
|
||||
const directionVector = parseVector3d(directionString);
|
||||
const rotationIndex = directionVector ? getRotationIndex(directionVector) : null;
|
||||
|
||||
const state = GetRoomEngine().getFurniState(roomId, objectId, isWallItem);
|
||||
|
||||
setFurniDirection(rotationIndex);
|
||||
setFurniState(state);
|
||||
}, [ avatarInfo, roomSession ]);
|
||||
|
||||
function parseVector3d(vectorString: string)
|
||||
{
|
||||
if (!vectorString) return null;
|
||||
|
||||
const matches = vectorString.match(/\[Vector3d: ([\d.]+), ([\d.]+), ([\d.]+)/);
|
||||
if (matches && matches.length === 4)
|
||||
{
|
||||
return {
|
||||
x: parseFloat(matches[1]),
|
||||
y: parseFloat(matches[2]),
|
||||
z: parseFloat(matches[3])
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
useMessageEvent<FurnitureFloorUpdateEvent>(FurnitureFloorUpdateEvent, event =>
|
||||
{
|
||||
const parser = event.getParser();
|
||||
const item = parser.item;
|
||||
|
||||
if (item.itemId !== avatarInfo.id) return;
|
||||
|
||||
const locationVector = {
|
||||
x: item.x,
|
||||
y: item.y,
|
||||
z: item.z
|
||||
};
|
||||
|
||||
if (locationVector)
|
||||
{
|
||||
setFurniLocationX(locationVector.x);
|
||||
setFurniLocationY(locationVector.y);
|
||||
setFurniLocationZ(locationVector.z);
|
||||
}
|
||||
|
||||
const directionVector = { x: item.direction };
|
||||
const rotationIndex = directionVector ? getRotationIndex(directionVector) : null;
|
||||
|
||||
const state = item.state;
|
||||
|
||||
setFurniDirection(rotationIndex);
|
||||
setFurniState(state);
|
||||
});
|
||||
|
||||
const handleHeightChange = useCallback((event) =>
|
||||
{
|
||||
let newZ = parseFloat(event.target.value);
|
||||
if (isNaN(newZ) || newZ < 0)
|
||||
{
|
||||
newZ = 0;
|
||||
}
|
||||
else if (newZ > 40)
|
||||
{
|
||||
newZ = 40;
|
||||
}
|
||||
setFurniLocationZ(newZ);
|
||||
sendUpdate(0, 0, newZ, 0);
|
||||
}, [ sendUpdate ]);
|
||||
|
||||
const handleHeightBlur = useCallback((event) =>
|
||||
{
|
||||
let newZ = parseFloat(event.target.value);
|
||||
if (isNaN(newZ) || newZ < 0)
|
||||
{
|
||||
newZ = 0;
|
||||
}
|
||||
else if (newZ > 40)
|
||||
{
|
||||
newZ = 40;
|
||||
}
|
||||
newZ = parseFloat(newZ.toFixed(4));
|
||||
setFurniLocationZ(newZ);
|
||||
sendUpdate(0, 0, newZ, 0);
|
||||
}, [ sendUpdate ]);
|
||||
|
||||
const adjustHeight = useCallback((amount) =>
|
||||
{
|
||||
let newZ = furniLocationZ + amount;
|
||||
if (newZ < 0)
|
||||
{
|
||||
newZ = 0;
|
||||
}
|
||||
else if (newZ > 40)
|
||||
{
|
||||
newZ = 40;
|
||||
}
|
||||
newZ = parseFloat(newZ.toFixed(4));
|
||||
setFurniLocationZ(newZ);
|
||||
sendUpdate(0, 0, newZ, 0);
|
||||
}, [ furniLocationZ, sendUpdate ]);
|
||||
|
||||
function getValidRoomObjectDirection(roomObject, isPositive)
|
||||
{
|
||||
if (!roomObject || !roomObject.model) return 0;
|
||||
|
||||
let allowedDirections = [];
|
||||
|
||||
if (roomObject.type === 'monster_plant')
|
||||
{
|
||||
allowedDirections = roomObject.model.getValue('pet_allowed_directions');
|
||||
}
|
||||
else
|
||||
{
|
||||
allowedDirections = roomObject.model.getValue('furniture_allowed_directions');
|
||||
}
|
||||
|
||||
let direction = roomObject.getDirection().x;
|
||||
|
||||
if (allowedDirections && allowedDirections.length)
|
||||
{
|
||||
let index = allowedDirections.indexOf(direction);
|
||||
|
||||
if (index < 0)
|
||||
{
|
||||
index = 0;
|
||||
for (let i = 0; i < allowedDirections.length; i++)
|
||||
{
|
||||
if (direction <= allowedDirections[i]) break;
|
||||
index++;
|
||||
}
|
||||
index = index % allowedDirections.length;
|
||||
}
|
||||
|
||||
if (isPositive)
|
||||
{
|
||||
index = (index + 1) % allowedDirections.length;
|
||||
}
|
||||
else
|
||||
{
|
||||
index = (index - 1 + allowedDirections.length) % allowedDirections.length;
|
||||
}
|
||||
|
||||
direction = allowedDirections[index];
|
||||
}
|
||||
|
||||
return direction;
|
||||
}
|
||||
|
||||
useSoundEvent<NowPlayingEvent>(NowPlayingEvent.NPE_SONG_CHANGED, event =>
|
||||
{
|
||||
setSongId(event.id);
|
||||
@ -241,7 +454,6 @@ export const InfoStandWidgetFurniView: FC<InfoStandWidgetFurniViewProps> = props
|
||||
if(furniKeys.length === 0 || furniValues.length === 0) return '';
|
||||
|
||||
let data = '';
|
||||
|
||||
let i = 0;
|
||||
|
||||
while(i < furniKeys.length)
|
||||
@ -250,7 +462,6 @@ export const InfoStandWidgetFurniView: FC<InfoStandWidgetFurniViewProps> = props
|
||||
const value = furniValues[i];
|
||||
|
||||
data = (data + (key + '=' + value + '\t'));
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
@ -296,7 +507,6 @@ export const InfoStandWidgetFurniView: FC<InfoStandWidgetFurniViewProps> = props
|
||||
for(const part of dataParts)
|
||||
{
|
||||
const [ key, value ] = part.split('=', 2);
|
||||
|
||||
mapData.set(key, value);
|
||||
}
|
||||
}
|
||||
@ -425,6 +635,63 @@ export const InfoStandWidgetFurniView: FC<InfoStandWidgetFurniViewProps> = props
|
||||
{ godMode &&
|
||||
<>
|
||||
<hr className="m-0" />
|
||||
{ (!avatarInfo.isWallItem && canMove) &&
|
||||
<>
|
||||
<Button className="infostand-buttons px-2" onClick={() => setDropdownOpen(!dropdownOpen)}>
|
||||
{dropdownOpen ? `${LocalizeText('widget.furni.present.close')} Buildtools` : `${LocalizeText('navigator.roomsettings.doormode.open')} Buildtools`}
|
||||
</Button>
|
||||
{ dropdownOpen &&
|
||||
<>
|
||||
<Flex gap={ 1 }>
|
||||
<Column className="buildtool-box buildtool-box-left">
|
||||
<Column fullWidth>
|
||||
<Text variant="white">{LocalizeText('group.edit.badge.position')}</Text>
|
||||
<Column justifyContent="center" alignItems="center"
|
||||
className="button-w-height">
|
||||
<Flex className="floor-spaceing">
|
||||
<Base className="buildtool-movefurni button-leftup" onClick={ () => sendUpdate(-1, 0, furniLocationZ, 0) }> <GrFormNextLink className="fa-icon icon-color" size="1.7em" /> </Base>
|
||||
<Base className="buildtool-movefurni button-rightup" onClick={ () => sendUpdate(0, -1, furniLocationZ, 0) }> <GrFormNextLink className="fa-icon icon-color" size="1.7em" /> </Base>
|
||||
</Flex>
|
||||
<Flex className="floor-spaceing">
|
||||
<Base className="buildtool-movefurni button-leftdown" onClick={ () => sendUpdate(0, 1, furniLocationZ, 0) }> <GrFormNextLink className="fa-icon icon-color" size="1.7em" /> </Base>
|
||||
<Base className="buildtool-movefurni button-rightdown" onClick={ () => sendUpdate(1, 0, furniLocationZ, 0) }> <GrFormNextLink className="fa-icon icon-color" size="1.7em" /> </Base>
|
||||
</Flex>
|
||||
</Column>
|
||||
<Text variant="white">{LocalizeText('infostand.button.rotate')}</Text>
|
||||
<Flex center className="floor-spaceing">
|
||||
<Base className="buildtool-rotatefurni" onClick={ () => sendUpdate(0, 0, furniLocationZ, -1) }> <GrRotateLeft className="fa-icon icon-color" size="1.4em" /> </Base>
|
||||
<Base className="buildtool-rotatefurni" onClick={ () => sendUpdate(0, 0, furniLocationZ, 1) }> <GrRotateRight className="fa-icon icon-color" size="1.4em" /> </Base>
|
||||
</Flex>
|
||||
</Column>
|
||||
</Column>
|
||||
<Column className="buildtool-box buildtool-box-right">
|
||||
<Column fullWidth>
|
||||
<Text variant="white">{LocalizeText('stack.magic.tile.height.label')}</Text>
|
||||
<input spellCheck="false" type="number" className="form-control form-control-sm" value={ furniLocationZ !== null ? furniLocationZ.toString() : '' } onChange={ handleHeightChange } onBlur={ handleHeightBlur } min={ 0 } max={ 40 } step={ 0.1 } />
|
||||
<Flex justifyContent="center" gap={ 1 }>
|
||||
<Column>
|
||||
<Base className="buildtool-setheight buildtool-setheightup" onClick={ () => adjustHeight(1) }>↑</Base>
|
||||
<Text variant="white" align="center">█</Text>
|
||||
<Base className="buildtool-setheight buildtool-setheightdown" onClick={ () => adjustHeight(-1) }>↓</Base>
|
||||
</Column>
|
||||
<Column>
|
||||
<Base className="buildtool-setheight buildtool-setheightup" onClick={ () => adjustHeight(0.1) }>↑</Base>
|
||||
<Text variant="white" align="center">▄</Text>
|
||||
<Base className="buildtool-setheight buildtool-setheightdown" onClick={ () => adjustHeight(-0.1) }>↓</Base>
|
||||
</Column>
|
||||
<Column>
|
||||
<Base className="buildtool-setheight buildtool-setheightup" onClick={ () => adjustHeight(0.01) }>↑</Base>
|
||||
<Text variant="white" align="center">_</Text>
|
||||
<Base className="buildtool-setheight buildtool-setheightdown" onClick={ () => adjustHeight(-0.01) }>↓</Base>
|
||||
</Column>
|
||||
</Flex>
|
||||
</Column>
|
||||
</Column>
|
||||
</Flex>
|
||||
</>
|
||||
}
|
||||
</>
|
||||
}
|
||||
{ (furniKeys.length > 0) &&
|
||||
<>
|
||||
<hr className="m-0"/>
|
||||
|
Loading…
x
Reference in New Issue
Block a user