Improve mod tools UI layout and usability

- Fix icon alignment using flexbox instead of absolute positioning
- Add active state indicators on buttons when sub-panels are open
- Add min-width constraints to prevent cramped layouts
- Improve user button with placeholder text and truncated username
- Improve room info panel with better spacing, clickable owner, colored owner status
- Improve chatlog with scrollable container, alternating row colors, compact headers
- Clean up room info header and room name display
This commit is contained in:
simoleo89
2026-03-16 23:10:27 +01:00
parent b6cbd5814c
commit f213e89122
4 changed files with 54 additions and 44 deletions
+18 -10
View File
@@ -117,23 +117,31 @@ export const ModToolsView: FC<{}> = props =>
return () => RemoveLinkEventTracker(linkTracker);
}, [ openRoomInfo, closeRoomInfo, toggleRoomInfo, openRoomChatlog, closeRoomChatlog, toggleRoomChatlog, openUserInfo, closeUserInfo, toggleUserInfo, openUserChatlog, closeUserChatlog, toggleUserChatlog ]);
const isRoomInfoOpen = currentRoomId > 0 && openRooms.includes(currentRoomId);
const isRoomChatlogOpen = currentRoomId > 0 && openRoomChatlogs.includes(currentRoomId);
const isUserInfoOpen = selectedUser && openUserInfos.includes(selectedUser.userId);
return (
<>
{ isVisible &&
<NitroCardView className="nitro-mod-tools" theme="primary-slim" uniqueKey="mod-tools" windowPosition={ DraggableWindowPosition.TOP_LEFT } >
<NitroCardView className="nitro-mod-tools min-w-[200px]" theme="primary-slim" uniqueKey="mod-tools" windowPosition={ DraggableWindowPosition.TOP_LEFT } >
<NitroCardHeaderView headerText={ 'Mod Tools' } onCloseClick={ event => setIsVisible(false) } />
<NitroCardContentView className="text-black" gap={ 1 }>
<Button className="relative" disabled={ (currentRoomId <= 0) } gap={ 1 } onClick={ event => CreateLinkEvent(`mod-tools/toggle-room-info/${ currentRoomId }`) }>
<div className="nitro-icon icon-small-room absolute inset-s-1" /> Room Tool
<NitroCardContentView className="text-black" gap={ 2 }>
<Button active={ isRoomInfoOpen } disabled={ (currentRoomId <= 0) } gap={ 2 } justifyContent="start" onClick={ event => CreateLinkEvent(`mod-tools/toggle-room-info/${ currentRoomId }`) }>
<div className="nitro-icon icon-small-room shrink-0" /> Room Tool
</Button>
<Button className="relative" disabled={ (currentRoomId <= 0) } gap={ 1 } innerRef={ elementRef } onClick={ event => CreateLinkEvent(`mod-tools/toggle-room-chatlog/${ currentRoomId }`) }>
<div className="nitro-icon icon-chat-history absolute inset-s-1" /> Chatlog Tool
<Button active={ isRoomChatlogOpen } disabled={ (currentRoomId <= 0) } gap={ 2 } innerRef={ elementRef } justifyContent="start" onClick={ event => CreateLinkEvent(`mod-tools/toggle-room-chatlog/${ currentRoomId }`) }>
<div className="nitro-icon icon-chat-history shrink-0" /> Chatlog Tool
</Button>
<Button className="relative" disabled={ !selectedUser } gap={ 1 } onClick={ () => CreateLinkEvent(`mod-tools/toggle-user-info/${ selectedUser.userId }`) }>
<div className="nitro-icon icon-user absolute inset-s-1" /> User: { selectedUser ? selectedUser.username : '' }
<Button active={ !!isUserInfoOpen } disabled={ !selectedUser } gap={ 2 } justifyContent="start" onClick={ () => CreateLinkEvent(`mod-tools/toggle-user-info/${ selectedUser.userId }`) }>
<div className="nitro-icon icon-user shrink-0" />
{ selectedUser
? <span className="truncate">{ selectedUser.username }</span>
: <span className="opacity-50 italic">Select a user</span>
}
</Button>
<Button className="relative" gap={ 1 } onClick={ () => setIsTicketsVisible(prevValue => !prevValue) }>
<div className="nitro-icon icon-tickets absolute inset-s-1" /> Report Tool
<Button active={ isTicketsVisible } gap={ 2 } justifyContent="start" onClick={ () => setIsTicketsVisible(prevValue => !prevValue) }>
<div className="nitro-icon icon-tickets shrink-0" /> Report Tool
</Button>
</NitroCardContentView>
</NitroCardView> }
@@ -46,14 +46,11 @@ export const ChatlogView: FC<ChatlogViewProps> = props =>
const RoomInfo = (props: { roomId: number, roomName: string }) =>
{
return (
<Flex alignItems="center" className="bg-muted rounded p-1" gap={ 2 } justifyContent="between">
<div className="flex gap-1">
<Text bold>Room name:</Text>
<Text>{ props.roomName }</Text>
</div>
<div className="flex gap-1">
<Button onClick={ event => TryVisitRoom(props.roomId) }>Visit Room</Button>
<Button onClick={ event => openRoomInfo(props.roomId) }>Room Tools</Button>
<Flex alignItems="center" className="bg-muted rounded p-2" gap={ 2 } justifyContent="between">
<Text bold truncate>{ props.roomName }</Text>
<div className="flex gap-1 shrink-0">
<Button size="sm" onClick={ event => TryVisitRoom(props.roomId) }>Visit</Button>
<Button size="sm" onClick={ event => openRoomInfo(props.roomId) }>Room Tools</Button>
</div>
</Flex>
);
@@ -63,7 +60,7 @@ export const ChatlogView: FC<ChatlogViewProps> = props =>
<>
<Column fit gap={ 0 } overflow="hidden">
<Column gap={ 2 }>
<Grid className="text-black font-bold border-bottom pb-1" gap={ 1 }>
<Grid className="text-black font-bold border-bottom pb-1 text-[11px] uppercase opacity-60 tracking-wider" gap={ 1 }>
<div className="col-span-2">Time</div>
<div className="col-span-3">User</div>
<div className="col-span-7">Message</div>
@@ -77,8 +74,8 @@ export const ChatlogView: FC<ChatlogViewProps> = props =>
{ row.isRoomInfo &&
<RoomInfo roomId={ row.roomId } roomName={ row.roomName } /> }
{ !row.isRoomInfo &&
<Grid alignItems="center" className="log-entry py-1 border-bottom" fullHeight={ false } gap={ 1 }>
<Text className="col-span-2">{ row.timestamp }</Text>
<Grid alignItems="center" className="log-entry py-1.5 border-bottom even:bg-black/[0.03]" fullHeight={ false } gap={ 1 }>
<Text className="col-span-2 opacity-60 text-[11px]">{ row.timestamp }</Text>
<Text bold pointer underline className="col-span-3" onClick={ event => CreateLinkEvent(`mod-tools/open-user-info/${ row.habboId }`) }>{ row.username }</Text>
<Text textBreak wrap className="col-span-7">{ row.message }</Text>
</Grid> }
@@ -33,9 +33,9 @@ export const ModToolsChatlogView: FC<ModToolsChatlogViewProps> = props =>
if(!roomChatlog) return null;
return (
<NitroCardView className="nitro-mod-tools-chatlog" theme="primary-slim" windowPosition={ DraggableWindowPosition.TOP_LEFT }>
<NitroCardHeaderView headerText={ `Room Chatlog ${ roomChatlog.roomName }` } onCloseClick={ onCloseClick } />
<NitroCardContentView className="text-black" overflow="hidden">
<NitroCardView className="nitro-mod-tools-chatlog min-w-[400px] max-h-[500px]" theme="primary-slim" windowPosition={ DraggableWindowPosition.TOP_LEFT }>
<NitroCardHeaderView headerText={ `Room Chatlog` } onCloseClick={ onCloseClick } />
<NitroCardContentView className="text-black" overflow="auto">
{ roomChatlog &&
<ChatlogView records={ [ roomChatlog ] } /> }
</NitroCardContentView>
@@ -69,47 +69,52 @@ export const ModToolsRoomView: FC<ModToolsRoomViewProps> = props =>
}, [ roomId, infoRequested, setInfoRequested ]);
return (
<NitroCardView className="nitro-mod-tools-room" theme="primary-slim" windowPosition={ DraggableWindowPosition.TOP_LEFT }>
<NitroCardHeaderView headerText={ 'Room Info' + (name ? ': ' + name : '') } onCloseClick={ event => onCloseClick() } />
<NitroCardContentView className="text-black">
<NitroCardView className="nitro-mod-tools-room min-w-[280px]" theme="primary-slim" windowPosition={ DraggableWindowPosition.TOP_LEFT }>
<NitroCardHeaderView headerText={ 'Room Info' } onCloseClick={ event => onCloseClick() } />
<NitroCardContentView className="text-black" gap={ 2 }>
{ name &&
<div className="bg-muted rounded px-2 py-1.5 text-center">
<Text bold truncate>{ name }</Text>
</div>
}
<div className="flex gap-2">
<Column grow gap={ 1 } justifyContent="center">
<div className="items-center gap-2">
<Text bold align="end" className="col-span-7">Room Owner:</Text>
<Text pointer truncate underline>{ ownerName }</Text>
<Column grow gap={ 1 }>
<div className="flex items-center gap-1">
<Text bold className="opacity-60 shrink-0">Owner:</Text>
<Text bold pointer truncate underline onClick={ () => CreateLinkEvent(`mod-tools/open-user-info/${ ownerId }`) }>{ ownerName }</Text>
</div>
<div className="items-center gap-2">
<Text bold align="end" className="col-span-7">Users in room:</Text>
<div className="flex items-center gap-1">
<Text bold className="opacity-60 shrink-0">Users in room:</Text>
<Text>{ usersInRoom }</Text>
</div>
<div className="items-center gap-2">
<Text bold align="end" className="col-span-7">Owner in room:</Text>
<Text>{ ownerInRoom ? 'Yes' : 'No' }</Text>
<div className="flex items-center gap-1">
<Text bold className="opacity-60 shrink-0">Owner here:</Text>
<Text className={ ownerInRoom ? 'text-green-700' : 'text-red-700' }>{ ownerInRoom ? 'Yes' : 'No' }</Text>
</div>
</Column>
<div className="flex flex-col gap-1">
<div className="flex flex-col gap-1 shrink-0">
<Button onClick={ event => TryVisitRoom(roomId) }>Visit Room</Button>
<Button onClick={ event => CreateLinkEvent(`mod-tools/open-room-chatlog/${ roomId }`) }>Chatlog</Button>
</div>
</div>
<Column className="bg-muted rounded p-2" gap={ 1 }>
<div className="flex items-center gap-1">
<div className="flex items-center gap-2">
<input checked={ kickUsers } className="form-check-input" type="checkbox" onChange={ event => setKickUsers(event.target.checked) } />
<Text small>Kick everyone out</Text>
</div>
<div className="flex items-center gap-1">
<div className="flex items-center gap-2">
<input checked={ lockRoom } className="form-check-input" type="checkbox" onChange={ event => setLockRoom(event.target.checked) } />
<Text small>Enable the doorbell</Text>
</div>
<div className="flex items-center gap-1">
<div className="flex items-center gap-2">
<input checked={ changeRoomName } className="form-check-input" type="checkbox" onChange={ event => setChangeRoomName(event.target.checked) } />
<Text small>Change room name</Text>
</div>
</Column>
<textarea className="min-h-[calc(1.5em+ .5rem+2px)] px-[.5rem] py-[.25rem] rounded-[.2rem]" placeholder="Type a mandatory message to the users in this text box..." value={ message } onChange={ event => setMessage(event.target.value) }></textarea>
<div className="flex justify-between">
<Button variant="danger" onClick={ event => handleClick('send_message') }>Send Caution</Button>
<Button onClick={ event => handleClick('alert_only') }>Send Alert only</Button>
<textarea className="min-h-[60px] px-2 py-1.5 rounded text-sm border border-black/10" placeholder="Type a mandatory message..." value={ message } onChange={ event => setMessage(event.target.value) }></textarea>
<div className="flex gap-2">
<Button className="grow" variant="danger" onClick={ event => handleClick('send_message') }>Send Caution</Button>
<Button className="grow" onClick={ event => handleClick('alert_only') }>Send Alert</Button>
</div>
</NitroCardContentView>
</NitroCardView>