🆙 Init V3

This commit is contained in:
DuckieTM
2026-01-31 09:10:52 +01:00
commit 7feb10ab15
1733 changed files with 53405 additions and 0 deletions
@@ -0,0 +1,106 @@
import { RoomDataParser } from '@nitrots/nitro-renderer';
import { FC, useRef, useState } from 'react';
import { FaUser } from 'react-icons/fa';
import { ArrowContainer, Popover } from 'react-tiny-popover';
import { LocalizeText } from '../../../../api';
import { Flex, LayoutBadgeImageView, LayoutRoomThumbnailView, NitroCardContentView, Text, UserProfileIconView } from '../../../../common';
export const NavigatorSearchResultItemInfoView: FC<{
roomData: RoomDataParser;
}> = props =>
{
const { roomData = null } = props;
const [ isVisible, setIsVisible ] = useState(false);
const elementRef = useRef<HTMLDivElement>();
const getUserCounterColor = () =>
{
const num: number = (100 * (roomData.userCount / roomData.maxUserCount));
let bg = 'bg-primary';
if(num >= 92)
{
bg = 'bg-danger';
}
else if(num >= 50)
{
bg = 'bg-warning';
}
else if(num > 0)
{
bg = 'bg-success';
}
return bg;
};
function dispatch(arg0: string): void
{
throw new Error('Function not implemented.');
}
return (
<>
<Popover
ref={ elementRef } // if you'd like a ref to your popover's child, you can grab one here
containerClassName="max-w-[276px] not-italic font-normal leading-normal text-left no-underline [text-shadow:none] normal-case tracking-[normal] [word-break:normal] [word-spacing:normal] whitespace-normal text-[.7875rem] [word-wrap:break-word] bg-[#dfdfdf] bg-clip-padding border-[1px] border-[solid] border-[#283F5D] rounded-[.25rem] [box-shadow:0_2px_#00000073] z-[1070]"
content={ ({ position, childRect, popoverRect }) => (
<ArrowContainer // if you'd like an arrow, you can import the ArrowContainer!
arrowColor={ 'black' }
arrowSize={ 7 }
arrowStyle={ { left: 'calc(-.5rem - 0px)' } }
childRect={ childRect }
popoverRect={ popoverRect }
position={ position }
>
<NitroCardContentView className="bg-transparent room-info image-rendering-pixelated" overflow="hidden">
<Flex gap={ 2 } overflow="hidden">
<LayoutRoomThumbnailView className="flex flex-col items-center mb-1 justify-content-end" customUrl={ roomData.officialRoomPicRef } roomId={ roomData.roomId }>
{ roomData.habboGroupId > 0 && (
<LayoutBadgeImageView badgeCode={ roomData.groupBadgeCode } className={ 'absolute top-0 start-0 m-1 ' } isGroup={ true } />) }
{ roomData.doorMode !== RoomDataParser.OPEN_STATE && (
<i className={ 'absolute end-0 mb-1 me-1 icon icon-navigator-room-' + (roomData.doorMode === RoomDataParser.DOORBELL_STATE ? 'locked' : roomData.doorMode === RoomDataParser.PASSWORD_STATE ? 'password' : roomData.doorMode === RoomDataParser.INVISIBLE_STATE ? 'invisible' : '') } />) }
</LayoutRoomThumbnailView>
<div className="flex flex-col gap-1">
<Text bold truncate className="flex-grow-1" style={ { maxHeight: 13 } }>
{ roomData.roomName }
</Text>
<div className="flex gap-2">
<Text italics variant="muted">
{ LocalizeText('navigator.roomownercaption') }
</Text>
<div className="flex items-center gap-1">
<UserProfileIconView userId={ roomData.ownerId } />
<Text italics>{ roomData.ownerName }</Text>
</div>
</div>
<Text className="flex-grow-1">
{ roomData.description }
</Text>
<Flex className={ 'badge p-1 absolute m-1 bottom-0 end-0 m-2 ' + getUserCounterColor() } gap={ 1 }>
<FaUser className="fa-icon" />
{ roomData.userCount }
</Flex>
</div>
</Flex>
</NitroCardContentView>
</ArrowContainer>
) }
isOpen={ isVisible }
padding={ 10 }
positions={ [ 'right' ] }
>
<div ref={ elementRef } className="cursor-pointer nitro-icon icon-navigator-info" onMouseLeave={ event => setIsVisible(false) } onMouseOver={ event => setIsVisible(true) } />
</Popover>
</>
);
};
@@ -0,0 +1,121 @@
import { GetSessionDataManager, RoomDataParser } from '@nitrots/nitro-renderer';
import { FC, MouseEvent } from 'react';
import { FaUser } from 'react-icons/fa';
import { CreateRoomSession, DoorStateType, TryVisitRoom } from '../../../../api';
import { Column, Flex, LayoutBadgeImageView, LayoutGridItemProps, LayoutRoomThumbnailView, Text } from '../../../../common';
import { useNavigator } from '../../../../hooks';
import { NavigatorSearchResultItemInfoView } from './NavigatorSearchResultItemInfoView';
export interface NavigatorSearchResultItemViewProps extends LayoutGridItemProps
{
roomData: RoomDataParser
thumbnail?: boolean
}
export const NavigatorSearchResultItemView: FC<NavigatorSearchResultItemViewProps> = props =>
{
const { roomData = null, children = null, thumbnail = false, ...rest } = props;
const { setDoorData = null } = useNavigator();
const getUserCounterColor = () =>
{
const num: number = (100 * (roomData.userCount / roomData.maxUserCount));
let bg = 'bg-primary';
if(num >= 92)
{
bg = 'bg-danger';
}
else if(num >= 50)
{
bg = 'bg-warning';
}
else if(num > 0)
{
bg = 'bg-success';
}
return bg;
};
const visitRoom = (event: MouseEvent) =>
{
if(roomData.ownerId !== GetSessionDataManager().userId)
{
if(roomData.habboGroupId !== 0)
{
TryVisitRoom(roomData.roomId);
return;
}
switch(roomData.doorMode)
{
case RoomDataParser.DOORBELL_STATE:
setDoorData(prevValue =>
{
const newValue = { ...prevValue };
newValue.roomInfo = roomData;
newValue.state = DoorStateType.START_DOORBELL;
return newValue;
});
return;
case RoomDataParser.PASSWORD_STATE:
setDoorData(prevValue =>
{
const newValue = { ...prevValue };
newValue.roomInfo = roomData;
newValue.state = DoorStateType.START_PASSWORD;
return newValue;
});
return;
}
}
CreateRoomSession(roomData.roomId);
};
if(thumbnail) return (
<Column pointer alignItems="center" className="navigator-item p-1 bg-light rounded-3 small mb-1 flex-col border border-muted" gap={ 0 } overflow="hidden" onClick={ visitRoom } { ...rest }>
<LayoutRoomThumbnailView className="flex flex-col items-center justify-end mb-1" customUrl={ roomData.officialRoomPicRef } roomId={ roomData.roomId }>
{ roomData.habboGroupId > 0 && <LayoutBadgeImageView badgeCode={ roomData.groupBadgeCode } className={ 'absolute top-0 start-0 m-1' } isGroup={ true } /> }
<Flex center className={ 'inline-block px-[.65em] py-[.35em] text-[.75em] font-bold leading-none text-[#fff] text-center whitespace-nowrap align-baseline rounded-[.25rem] p-1 absolute m-1 ' + getUserCounterColor() } gap={ 1 }>
<FaUser className="fa-icon" />
{ roomData.userCount }
</Flex>
{ (roomData.doorMode !== RoomDataParser.OPEN_STATE) &&
<i className={ ('absolute end-0 mb-1 me-1 icon icon-navigator-room-' + ((roomData.doorMode === RoomDataParser.DOORBELL_STATE) ? 'locked' : (roomData.doorMode === RoomDataParser.PASSWORD_STATE) ? 'password' : (roomData.doorMode === RoomDataParser.INVISIBLE_STATE) ? 'invisible' : '')) } /> }
</LayoutRoomThumbnailView>
<Flex className="w-full">
<Text truncate className="!flex-grow">{ roomData.roomName }</Text>
<Flex reverse alignItems="center" gap={ 1 }>
<NavigatorSearchResultItemInfoView roomData={ roomData } />
</Flex>
{ children }
</Flex>
</Column>
);
return (
<Flex pointer alignItems="center" className="navigator-item px-2 py-1 small" gap={ 2 } overflow="hidden" onClick={ visitRoom } { ...rest }>
<Flex center className={ 'inline-block px-[.65em] py-[.35em] text-[.75em] font-bold leading-none text-[#fff] text-center whitespace-nowrap align-baseline rounded-[.25rem] p-1 ' + getUserCounterColor() } gap={ 1 }>
<FaUser className="fa-icon" />
{ roomData.userCount }
</Flex>
<Text grow truncate>{ roomData.roomName }</Text>
<Flex reverse alignItems="center" gap={ 1 }>
<NavigatorSearchResultItemInfoView roomData={ roomData } />
{ roomData.habboGroupId > 0 && <i className="nitro-icon icon-navigator-room-group" /> }
{ (roomData.doorMode !== RoomDataParser.OPEN_STATE) &&
<i className={ ('nitro-icon icon-navigator-room-' + ((roomData.doorMode === RoomDataParser.DOORBELL_STATE) ? 'locked' : (roomData.doorMode === RoomDataParser.PASSWORD_STATE) ? 'password' : (roomData.doorMode === RoomDataParser.INVISIBLE_STATE) ? 'invisible' : '')) } /> }
</Flex>
{ children }
</Flex>
);
};
@@ -0,0 +1,118 @@
import { NavigatorSearchComposer, NavigatorSearchResultList } from '@nitrots/nitro-renderer';
import { FC, useEffect, useState } from 'react';
import { FaBars, FaMinus, FaPlus, FaTh, FaWindowMaximize, FaWindowRestore } from 'react-icons/fa';
import { LocalizeText, NavigatorSearchResultViewDisplayMode, SendMessageComposer } from '../../../../api';
import { AutoGrid, AutoGridProps, Column, Flex, Grid, Text } from '../../../../common';
import { useNavigator } from '../../../../hooks';
import { NavigatorSearchResultItemView } from './NavigatorSearchResultItemView';
export interface NavigatorSearchResultViewProps extends AutoGridProps
{
searchResult: NavigatorSearchResultList;
}
export const NavigatorSearchResultView: FC<NavigatorSearchResultViewProps> = props =>
{
const { searchResult = null, ...rest } = props;
const [ isExtended, setIsExtended ] = useState(true);
const [ displayMode, setDisplayMode ] = useState<number>(0);
const { topLevelContext = null } = useNavigator();
const getResultTitle = () =>
{
let name = searchResult.code;
if(!name || !name.length || LocalizeText('navigator.searchcode.title.' + name) == ('navigator.searchcode.title.' + name)) return searchResult.data;
if(name.startsWith('${')) return name.slice(2, (name.length - 1));
return ('navigator.searchcode.title.' + name);
};
const toggleDisplayMode = () =>
{
setDisplayMode(prevValue =>
{
if(prevValue === NavigatorSearchResultViewDisplayMode.LIST) return NavigatorSearchResultViewDisplayMode.THUMBNAILS;
return NavigatorSearchResultViewDisplayMode.LIST;
});
};
const showMore = () =>
{
if(searchResult.action == 1) SendMessageComposer(new NavigatorSearchComposer(searchResult.code, ''));
else if(searchResult.action == 2 && topLevelContext) SendMessageComposer(new NavigatorSearchComposer(topLevelContext.code, ''));
};
useEffect(() =>
{
if(!searchResult) return;
setIsExtended(!searchResult.closed);
setDisplayMode(searchResult.mode);
}, [ searchResult ]);
const gridHasTwoColumns = (displayMode >= NavigatorSearchResultViewDisplayMode.THUMBNAILS);
return (
<Column className="bg-white rounded border border-muted" gap={ 0 }>
<Flex fullWidth alignItems="center" className="px-2 py-1" justifyContent="between">
<Flex grow pointer alignItems="center" gap={ 1 } onClick={ event => setIsExtended(prevValue => !prevValue) }>
{ isExtended && <FaMinus className="text-secondary fa-icon" /> }
{ !isExtended && <FaPlus className="text-secondary fa-icon" /> }
<Text>{ LocalizeText(getResultTitle()) }</Text>
</Flex>
<div className="flex gap-2">
{ (displayMode === NavigatorSearchResultViewDisplayMode.LIST) && <FaTh className="text-secondary fa-icon" onClick={ toggleDisplayMode } /> }
{ (displayMode >= NavigatorSearchResultViewDisplayMode.THUMBNAILS) && <FaBars className="text-secondary fa-icon" onClick={ toggleDisplayMode } /> }
{ (searchResult.action > 0) && (searchResult.action === 1) && <FaWindowMaximize className="text-secondary fa-icon" onClick={ showMore } /> }
{ (searchResult.action > 0) && (searchResult.action !== 1) && <FaWindowRestore className="text-secondary fa-icon" onClick={ showMore } /> }
</div>
</Flex> { isExtended &&
<>
{
gridHasTwoColumns ? <AutoGrid columnCount={ 3 } { ...rest } className="mx-2" columnMinHeight={ 130 } columnMinWidth={ 110 }>
{ searchResult.rooms.length > 0 && searchResult.rooms.map((room, index) => <NavigatorSearchResultItemView key={ index } roomData={ room } thumbnail={ true } />) }
</AutoGrid> : <Grid className="navigator-grid" columnCount={ 1 } gap={ 0 }>
{ searchResult.rooms.length > 0 && searchResult.rooms.map((room, index) => <NavigatorSearchResultItemView key={ index } roomData={ room } />) }
</Grid>
}
</>
}
</Column>
// <div className="nitro-navigator-search-result bg-white rounded mb-2 overflow-hidden">
// <div className="flex flex-col">
// <div className="flex items-center px-2 py-1 text-black">
// <i className={ 'text-secondary fas ' + (isExtended ? 'fa-minus' : 'fa-plus') } onClick={ toggleExtended }></i>
// <div className="ms-2 !flex-grow">{ LocalizeText(getResultTitle()) }</div>
// <i className={ 'text-secondary fas ' + classNames({ 'fa-bars': (displayMode === NavigatorSearchResultViewDisplayMode.LIST), 'fa-th': displayMode >= NavigatorSearchResultViewDisplayMode.THUMBNAILS })}></i>
// </div>
// { isExtended &&
// <div className={ 'nitro-navigator-result-list row row-cols-' + classNames({ '1': (displayMode === NavigatorSearchResultViewDisplayMode.LIST), '2': (displayMode >= NavigatorSearchResultViewDisplayMode.THUMBNAILS) }) }>
// { searchResult.rooms.length > 0 && searchResult.rooms.map((room, index) =>
// {
// return <NavigatorSearchResultItemView key={ index } roomData={ room } />
// }) }
// </div> }
// </div>
// </div>
// <div className="nitro-navigator-result-list p-2">
// <div className="flex mb-2 small cursor-pointer" onClick={ toggleList }>
// <i className={ "fas " + classNames({ 'fa-plus': !isExtended, 'fa-minus': isExtended })}></i>
// <div className="align-self-center w-full ml-2">{ LocalizeText(getListCode()) }</div>
// <i className={ "fas " + classNames({ 'fa-bars': displayMode === NavigatorResultListViewDisplayMode.LIST, 'fa-th': displayMode >= NavigatorResultListViewDisplayMode.THUMBNAILS })} onClick={ toggleDisplayMode }></i>
// </div>
// <div className={ 'row mr-n2 row-cols-' + classNames({ '1': displayMode === NavigatorResultListViewDisplayMode.LIST, '2': displayMode >= NavigatorResultListViewDisplayMode.THUMBNAILS }) }>
// { isExtended && resultList && resultList.rooms.map((room, index) =>
// {
// return <NavigatorResultView key={ index } result={ room } />
// })
// }
// </div>
// </div>
);
};
@@ -0,0 +1,81 @@
import { FC, KeyboardEvent, useEffect, useState } from 'react';
import { FaSearch } from 'react-icons/fa';
import { INavigatorSearchFilter, LocalizeText, SearchFilterOptions } from '../../../../api';
import { Button } from '../../../../common';
import { useNavigator } from '../../../../hooks';
export const NavigatorSearchView: FC<{
sendSearch: (searchValue: string, contextCode: string) => void;
}> = props =>
{
const { sendSearch = null } = props;
const [ searchFilterIndex, setSearchFilterIndex ] = useState(0);
const [ searchValue, setSearchValue ] = useState('');
const { topLevelContext = null, searchResult = null } = useNavigator();
const processSearch = () =>
{
if(!topLevelContext) return;
let searchFilter = SearchFilterOptions[searchFilterIndex];
if(!searchFilter) searchFilter = SearchFilterOptions[0];
const searchQuery = ((searchFilter.query ? (searchFilter.query + ':') : '') + searchValue);
sendSearch((searchQuery || ''), topLevelContext.code);
};
const handleKeyDown = (event: KeyboardEvent<HTMLInputElement>) =>
{
if(event.key !== 'Enter') return;
processSearch();
};
useEffect(() =>
{
if(!searchResult) return;
const split = searchResult.data.split(':');
let filter: INavigatorSearchFilter = null;
let value: string = '';
if(split.length >= 2)
{
const [ query, ...rest ] = split;
filter = SearchFilterOptions.find(option => (option.query === query));
value = rest.join(':');
}
else
{
value = searchResult.data;
}
if(!filter) filter = SearchFilterOptions[0];
setSearchFilterIndex(SearchFilterOptions.findIndex(option => (option === filter)));
setSearchValue(value);
}, [ searchResult ]);
return (
<div className="flex w-full gap-1">
<div className="flex shrink-0">
<select className="form-select" value={ searchFilterIndex } onChange={ event => setSearchFilterIndex(parseInt(event.target.value)) }>
{ SearchFilterOptions.map((filter, index) =>
{
return <option key={ index } value={ index }>{ LocalizeText('navigator.filter.' + filter.name) }</option>;
}) }
</select>
</div>
<div className="flex w-full gap-1">
<input className="w-full form-control" placeholder={ LocalizeText('navigator.filter.input.placeholder') } type="text" value={ searchValue } onChange={ event => setSearchValue(event.target.value) } onKeyDown={ event => handleKeyDown(event) } />
<Button variant="primary" onClick={ processSearch }>
<FaSearch className="fa-icon" />
</Button>
</div>
</div>
);
};