mirror of
https://github.com/duckietm/Nitro-V3.git
synced 2026-06-21 07:56:20 +00:00
🆙 Init V3
This commit is contained in:
@@ -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>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user