fix: polish furniture widgets and area hide toggle

This commit is contained in:
Lorenzune
2026-04-03 13:00:05 +02:00
parent e4b1f14fa2
commit c4e1318fd5
6 changed files with 227 additions and 42 deletions
@@ -1,28 +1,84 @@
import { FC } from 'react';
import { ColorConverter } from '@nitrots/nitro-renderer';
import { FC, useMemo } from 'react';
import { ColorUtils, LocalizeText } from '../../../../api';
import { Button, NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../common';
import { Button, NitroCardContentView, NitroCardHeaderView, NitroCardView, Slider, Text } from '../../../../common';
import { useFurnitureBackgroundColorWidget } from '../../../../hooks';
export const FurnitureBackgroundColorView: FC<{}> = props =>
{
const { objectId = -1, color = 0, setColor = null, applyToner = null, toggleToner = null, onClose = null } = useFurnitureBackgroundColorWidget();
const { objectId = -1, hue = 0, saturation = 0, lightness = 0, setHue = null, setSaturation = null, setLightness = null, applyToner = null, toggleToner = null, onClose = null } = useFurnitureBackgroundColorWidget();
const previewColor = useMemo(() =>
{
const hsl = ColorUtils.eight_bitVals_to_int(0, hue, saturation, lightness);
return ColorConverter.hslToRGB(hsl);
}, [ hue, saturation, lightness ]);
if(objectId === -1) return null;
return (
<NitroCardView className="nitro-room-widget-toner" theme="primary-slim">
<NitroCardHeaderView headerText={ LocalizeText('widget.backgroundcolor.title') } onCloseClick={ onClose } />
<NitroCardContentView justifyContent="between" overflow="hidden">
<div className="flex flex-col gap-1 overflow-auto">
<input className="min-h-[calc(1.5em+ .5rem+2px)] px-[.5rem] py-[.25rem] rounded-[.2rem]" type="color" value={ ColorUtils.makeColorNumberHex(color) } onChange={ event => setColor(ColorUtils.convertFromHex(event.target.value)) } />
<NitroCardView className="nitro-room-widget-background-color" theme="primary-slim">
<NitroCardHeaderView headerText={ LocalizeText('widget.backgroundcolour.title') } onCloseClick={ onClose } />
<NitroCardContentView classNames={ [ 'bgcolor-widget-content' ] } overflow="hidden">
<div className="bgcolor-widget-panel">
<div className="bgcolor-widget-top">
<Text className="bgcolor-widget-info">{ LocalizeText('widget.backgroundcolor.info') }</Text>
<div className="bgcolor-widget-preview" style={ { backgroundColor: ColorUtils.makeColorNumberHex(previewColor) } } />
</div>
<div className="bgcolor-widget-slider-group">
<Text fontWeight="bold" className="bgcolor-widget-label">{ LocalizeText('widget.backgroundcolor.hue') }</Text>
<div className="bgcolor-widget-slider-shell">
<Slider
disabledButton
max={ 255 }
min={ 0 }
step={ 1 }
thumbClassName="bgcolor-widget-slider-thumb"
trackClassName="bgcolor-widget-slider-track"
value={ hue }
renderThumb={ props => <div { ...props } /> }
onChange={ value => setHue(value as number) } />
</div>
</div>
<div className="bgcolor-widget-slider-group">
<Text fontWeight="bold" className="bgcolor-widget-label">{ LocalizeText('widget.backgroundcolor.saturation') }</Text>
<div className="bgcolor-widget-slider-shell">
<Slider
disabledButton
max={ 255 }
min={ 0 }
step={ 1 }
thumbClassName="bgcolor-widget-slider-thumb"
trackClassName="bgcolor-widget-slider-track"
value={ saturation }
renderThumb={ props => <div { ...props } /> }
onChange={ value => setSaturation(value as number) } />
</div>
</div>
<div className="bgcolor-widget-slider-group">
<Text fontWeight="bold" className="bgcolor-widget-label">{ LocalizeText('widget.backgroundcolor.lightness') }</Text>
<div className="bgcolor-widget-slider-shell">
<Slider
disabledButton
max={ 255 }
min={ 0 }
step={ 1 }
thumbClassName="bgcolor-widget-slider-thumb"
trackClassName="bgcolor-widget-slider-track"
value={ lightness }
renderThumb={ props => <div { ...props } /> }
onChange={ value => setLightness(value as number) } />
</div>
</div>
</div>
<div className="flex flex-col gap-1">
<Button fullWidth variant="primary" onClick={ toggleToner }>
{ LocalizeText('widget.backgroundcolor.button.on') }
</Button>
<Button fullWidth variant="primary" onClick={ applyToner }>
<div className="bgcolor-widget-actions">
<Button classNames={ [ 'bgcolor-widget-button' ] } onClick={ applyToner }>
{ LocalizeText('widget.backgroundcolor.button.apply') }
</Button>
<Button classNames={ [ 'bgcolor-widget-button' ] } onClick={ toggleToner }>
{ LocalizeText('widget.backgroundcolor.button.on') }
</Button>
</div>
</NitroCardContentView>
</NitroCardView>
@@ -6,8 +6,10 @@ import { useFurnitureStackHeightWidget } from '../../../../hooks';
export const FurnitureStackHeightView: FC<{}> = props =>
{
const { objectId = -1, height = 0, maxHeight = 40, onClose = null, updateHeight = null } = useFurnitureStackHeightWidget();
const { objectId = -1, height = 0, maxHeight = 40, isWalkHeightHelper = false, onClose = null, updateHeight = null } = useFurnitureStackHeightWidget();
const [ tempHeight, setTempHeight ] = useState('');
const titleKey = isWalkHeightHelper ? 'widget.custom.walk.height.title' : 'widget.custom.stack.height.title';
const textKey = isWalkHeightHelper ? 'widget.custom.walk.height.text' : 'widget.custom.stack.height.text';
const updateTempHeight = (value: string) =>
{
@@ -29,9 +31,9 @@ export const FurnitureStackHeightView: FC<{}> = props =>
return (
<NitroCardView className="nitro-widget-custom-stack-height" theme="primary-slim">
<NitroCardHeaderView headerText={ LocalizeText('widget.custom.stack.height.title') } onCloseClick={ onClose } />
<NitroCardHeaderView headerText={ LocalizeText(titleKey) } onCloseClick={ onClose } />
<NitroCardContentView justifyContent="between">
<Text>{ LocalizeText('widget.custom.stack.height.text') }</Text>
<Text>{ LocalizeText(textKey) }</Text>
<div className="flex gap-2">
<Slider
max={ maxHeight }
+131
View File
@@ -19,6 +19,137 @@
width: 190px;
}
.nitro-room-widget-background-color {
width: 314px;
.bgcolor-widget-content {
gap: 10px;
padding: 8px;
}
.bgcolor-widget-panel {
display: flex;
flex-direction: column;
gap: 10px;
min-height: 154px;
padding: 10px;
border: 1px solid #9c9c9c;
border-radius: 2px;
background: #f3f3f1;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.9);
}
.bgcolor-widget-top {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 12px;
}
.bgcolor-widget-info {
color: #111;
font-size: 12px;
line-height: 1.2;
}
.bgcolor-widget-preview {
flex-shrink: 0;
width: 32px;
height: 32px;
border: 1px solid #b4b4a3;
background: #c8ce8c;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.7);
}
.bgcolor-widget-slider-group {
display: flex;
flex-direction: column;
gap: 3px;
}
.bgcolor-widget-label {
color: #000;
font-size: 12px;
line-height: 1;
}
.bgcolor-widget-slider-shell {
position: relative;
width: 100%;
padding: 2px 0;
}
.bgcolor-widget-slider-shell::before {
content: '';
position: absolute;
left: 4px;
right: 4px;
top: 50%;
height: 8px;
transform: translateY(-50%);
background:
repeating-linear-gradient(
to right,
transparent 0 9px,
#1a1a1a 9px 10px
);
opacity: 0.75;
pointer-events: none;
}
.bgcolor-widget-slider-shell .nitro-slider-wrapper {
position: relative;
z-index: 1;
gap: 0;
}
.bgcolor-widget-slider-shell .nitro-slider {
height: 16px;
}
.bgcolor-widget-slider-track {
height: 2px !important;
border: 0 !important;
border-radius: 999px;
box-shadow: none !important;
&.track-0,
&.track-1 {
background: #000 !important;
}
}
.bgcolor-widget-slider-thumb {
width: 11px !important;
height: 18px !important;
margin-top: -1px;
border-radius: 4px !important;
border: 1px solid #345b72 !important;
background: linear-gradient(180deg, #6ba7c7 0%, #4a87a9 100%) !important;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.55), 0 1px 0 rgba(0, 0, 0, 0.25);
cursor: pointer;
}
.bgcolor-widget-actions {
display: flex;
justify-content: space-between;
gap: 8px;
}
.bgcolor-widget-button {
min-width: 74px;
color: #000 !important;
border-color: #7f7f7f !important;
background: linear-gradient(180deg, #f7f7f7 0%, #d9d9d9 100%) !important;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.95), 0 1px 0 rgba(0, 0, 0, 0.15) !important;
}
.bgcolor-widget-button:hover {
background: linear-gradient(180deg, #ffffff 0%, #e6e6e6 100%) !important;
border-color: #767676 !important;
}
}
.nitro-room-widget-dimmer {
width: 275px;
@@ -1,4 +1,4 @@
import { FurnitureMultiStateComposer, GetRoomEngine, RoomAreaSelectionManager, RoomEngineAreaHideStateEvent, RoomEngineTriggerWidgetEvent, RoomObjectVariable, SetObjectDataMessageComposer } from '@nitrots/nitro-renderer';
import { GetRoomEngine, RoomAreaSelectionManager, RoomEngineAreaHideStateEvent, RoomEngineTriggerWidgetEvent, RoomObjectVariable, SetObjectDataMessageComposer } from '@nitrots/nitro-renderer';
import { useCallback, useEffect, useState } from 'react';
import { CanManipulateFurniture, SendMessageComposer } from '../../../../api';
import { useNitroEvent } from '../../../events';
@@ -38,9 +38,10 @@ const useFurnitureAreaHideWidgetState = () =>
{
if(objectId === -1) return;
const nextState = (isOn ? 0 : 1);
const data = new Map<string, string>();
data.set('state', isOn ? '1' : '0');
data.set('state', nextState.toString());
data.set('rootX', rootX.toString());
data.set('rootY', rootY.toString());
data.set('width', width.toString());
@@ -50,7 +51,6 @@ const useFurnitureAreaHideWidgetState = () =>
data.set('invert', inverted ? '1' : '0');
SendMessageComposer(new SetObjectDataMessageComposer(objectId, data));
SendMessageComposer(new FurnitureMultiStateComposer(objectId, isOn ? 0 : 1));
onClose();
}, [ objectId, isOn, rootX, rootY, width, length, invisibility, wallItems, inverted ]);
@@ -1,6 +1,6 @@
import { ApplyTonerComposer, ColorConverter, GetRoomEngine, RoomEngineTriggerWidgetEvent, RoomObjectVariable } from '@nitrots/nitro-renderer';
import { ApplyTonerComposer, GetRoomEngine, RoomEngineTriggerWidgetEvent, RoomObjectVariable } from '@nitrots/nitro-renderer';
import { useEffect, useState } from 'react';
import { CanManipulateFurniture, ColorUtils, DispatchUiEvent, RoomWidgetUpdateBackgroundColorPreviewEvent, SendMessageComposer } from '../../../../api';
import { CanManipulateFurniture, DispatchUiEvent, RoomWidgetUpdateBackgroundColorPreviewEvent, SendMessageComposer } from '../../../../api';
import { useNitroEvent } from '../../../events';
import { useFurniRemovedEvent } from '../../engine';
import { useRoom } from '../../useRoom';
@@ -9,15 +9,12 @@ const useFurnitureBackgroundColorWidgetState = () =>
{
const [ objectId, setObjectId ] = useState(-1);
const [ category, setCategory ] = useState(-1);
const [ color, setColor ] = useState(0);
const [ hue, setHue ] = useState(0);
const [ saturation, setSaturation ] = useState(0);
const [ lightness, setLightness ] = useState(0);
const { roomSession = null } = useRoom();
const applyToner = () =>
{
const hsl = ColorConverter.rgbToHSL(color);
const [ _, hue, saturation, lightness ] = ColorUtils.int_to_8BitVals(hsl);
SendMessageComposer(new ApplyTonerComposer(objectId, hue, saturation, lightness));
};
const applyToner = () => SendMessageComposer(new ApplyTonerComposer(objectId, hue, saturation, lightness));
const toggleToner = () => roomSession.useMultistateItem(objectId);
@@ -27,7 +24,9 @@ const useFurnitureBackgroundColorWidgetState = () =>
setObjectId(-1);
setCategory(-1);
setColor(0);
setHue(0);
setSaturation(0);
setLightness(0);
};
useNitroEvent<RoomEngineTriggerWidgetEvent>(RoomEngineTriggerWidgetEvent.REQUEST_BACKGROUND_COLOR, event =>
@@ -39,14 +38,9 @@ const useFurnitureBackgroundColorWidgetState = () =>
setObjectId(event.objectId);
setCategory(event.category);
const hue = parseInt(model.getValue<string>(RoomObjectVariable.FURNITURE_ROOM_BACKGROUND_COLOR_HUE));
const saturation = parseInt(model.getValue<string>(RoomObjectVariable.FURNITURE_ROOM_BACKGROUND_COLOR_SATURATION));
const light = parseInt(model.getValue<string>(RoomObjectVariable.FURNITURE_ROOM_BACKGROUND_COLOR_LIGHTNESS));
const hsl = ColorUtils.eight_bitVals_to_int(0, hue,saturation,light);
const rgbColor = ColorConverter.hslToRGB(hsl);
setColor(rgbColor);
setHue(parseInt(model.getValue<string>(RoomObjectVariable.FURNITURE_ROOM_BACKGROUND_COLOR_HUE)) || 0);
setSaturation(parseInt(model.getValue<string>(RoomObjectVariable.FURNITURE_ROOM_BACKGROUND_COLOR_SATURATION)) || 0);
setLightness(parseInt(model.getValue<string>(RoomObjectVariable.FURNITURE_ROOM_BACKGROUND_COLOR_LIGHTNESS)) || 0);
});
useFurniRemovedEvent(((objectId !== -1) && (category !== -1)), event =>
@@ -60,12 +54,10 @@ const useFurnitureBackgroundColorWidgetState = () =>
{
if((objectId === -1) || (category === -1)) return;
const hls = ColorConverter.rgbToHSL(color);
const [ _, hue, saturation, lightness ] = ColorUtils.int_to_8BitVals(hls);
DispatchUiEvent(new RoomWidgetUpdateBackgroundColorPreviewEvent(RoomWidgetUpdateBackgroundColorPreviewEvent.PREVIEW, hue, saturation, lightness));
}, [ objectId, category, color ]);
}, [ objectId, category, hue, saturation, lightness ]);
return { objectId, color, setColor, applyToner, toggleToner, onClose };
return { objectId, hue, saturation, lightness, setHue, setSaturation, setLightness, applyToner, toggleToner, onClose };
};
export const useFurnitureBackgroundColorWidget = useFurnitureBackgroundColorWidgetState;
@@ -5,6 +5,7 @@ import { useMessageEvent, useNitroEvent } from '../../../events';
import { useFurniRemovedEvent } from '../../engine';
const MAX_HEIGHT: number = 40;
const WALK_HEIGHT_HELPER_MODEL_KEY = 'furniture_is_walk_height_helper';
const useFurnitureStackHeightWidgetState = () =>
{
@@ -12,6 +13,7 @@ const useFurnitureStackHeightWidgetState = () =>
const [ category, setCategory ] = useState(-1);
const [ height, setHeight ] = useState(0);
const [ pendingHeight, setPendingHeight ] = useState(-1);
const [ isWalkHeightHelper, setIsWalkHeightHelper ] = useState(false);
const onClose = () =>
{
@@ -19,6 +21,7 @@ const useFurnitureStackHeightWidgetState = () =>
setCategory(-1);
setHeight(0);
setPendingHeight(-1);
setIsWalkHeightHelper(false);
};
const updateHeight = (height: number, server: boolean = false) =>
@@ -55,6 +58,7 @@ const useFurnitureStackHeightWidgetState = () =>
setCategory(event.category);
setHeight(roomObject.getLocation().z);
setPendingHeight(-1);
setIsWalkHeightHelper(roomObject.model?.getValue<number>(WALK_HEIGHT_HELPER_MODEL_KEY) > 0);
});
useFurniRemovedEvent(((objectId !== -1) && (category !== -1)), event =>
@@ -73,7 +77,7 @@ const useFurnitureStackHeightWidgetState = () =>
return () => clearTimeout(timeout);
}, [ objectId, pendingHeight ]);
return { objectId, height, maxHeight: MAX_HEIGHT, onClose, updateHeight };
return { objectId, height, maxHeight: MAX_HEIGHT, isWalkHeightHelper, onClose, updateHeight };
};
export const useFurnitureStackHeightWidget = useFurnitureStackHeightWidgetState;