mirror of
https://github.com/billsonnn/nitro-react.git
synced 2024-11-22 22:30:52 +01:00
Update virtual lists
This commit is contained in:
parent
b91f5eaed8
commit
1ef9190fab
@ -17,6 +17,7 @@
|
||||
"@fortawesome/free-solid-svg-icons": "^6.1.1",
|
||||
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||
"@nitrots/nitro-renderer": "^1.3.4",
|
||||
"@tanstack/react-virtual": "^3.0.0-beta.18",
|
||||
"animate.css": "^4.1.1",
|
||||
"classnames": "^2.3.1",
|
||||
"cross-env": "^7.0.3",
|
||||
@ -27,7 +28,6 @@
|
||||
"react-scripts": "4.0.3",
|
||||
"react-slider": "^2.0.0",
|
||||
"react-transition-group": "^4.4.2",
|
||||
"react-virtualized": "^9.22.3",
|
||||
"react-youtube": "^7.13.1",
|
||||
"sass": "^1.53.0",
|
||||
"typescript": "^4.3.5",
|
||||
@ -44,7 +44,6 @@
|
||||
"@types/react-dom": "^18.0.6",
|
||||
"@types/react-slider": "^1.3.1",
|
||||
"@types/react-transition-group": "^4.4.5",
|
||||
"@types/react-virtualized": "^9.21.21",
|
||||
"@typescript-eslint/eslint-plugin": "^5.30.7",
|
||||
"@typescript-eslint/parser": "^5.30.7",
|
||||
"eslint": "^8.20.0",
|
||||
|
64
src/common/InfiniteScroll.tsx
Normal file
64
src/common/InfiniteScroll.tsx
Normal file
@ -0,0 +1,64 @@
|
||||
import { useVirtualizer } from '@tanstack/react-virtual';
|
||||
import { FC, ReactElement, useEffect, useRef } from 'react';
|
||||
import { Base } from './Base';
|
||||
|
||||
interface InfiniteScrollProps<T = any>
|
||||
{
|
||||
rows: T[];
|
||||
estimateSize: number;
|
||||
overscan?: number;
|
||||
rowRender: (row: T) => ReactElement;
|
||||
}
|
||||
|
||||
export const InfiniteScroll: FC<InfiniteScrollProps> = props =>
|
||||
{
|
||||
const { rows = [], estimateSize = 0, overscan = 5, rowRender = null } = props;
|
||||
const elementRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const rowVirtualizer = useVirtualizer({
|
||||
count: rows.length,
|
||||
getScrollElement: () => elementRef?.current,
|
||||
estimateSize: () => estimateSize,
|
||||
overscan: overscan
|
||||
});
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
let timeout: ReturnType<typeof setTimeout> = null;
|
||||
|
||||
const resizeObserver = new ResizeObserver(() =>
|
||||
{
|
||||
if(timeout) clearTimeout(timeout);
|
||||
|
||||
if(!elementRef.current) return;
|
||||
|
||||
timeout = setTimeout(() => rowVirtualizer.getVirtualItems().forEach((virtualItem, index) => virtualItem.measureElement(elementRef?.current?.children?.[0]?.children[index])), 10);
|
||||
});
|
||||
|
||||
if(elementRef.current) resizeObserver.observe(elementRef.current);
|
||||
|
||||
return () =>
|
||||
{
|
||||
if(timeout) clearTimeout();
|
||||
|
||||
timeout = null;
|
||||
|
||||
resizeObserver.disconnect();
|
||||
}
|
||||
}, [ rowVirtualizer ]);
|
||||
|
||||
return (
|
||||
<Base fit innerRef={ elementRef } position="relative" overflow="auto">
|
||||
<Base style={ { height: rowVirtualizer.getTotalSize() } }>
|
||||
{ rowVirtualizer.getVirtualItems().map(virtualRow =>
|
||||
{
|
||||
return (
|
||||
<div key={ virtualRow.index } ref={ virtualRow.measureElement } style={ { transform: `translateY(${ virtualRow.start }px)`, position: 'absolute', width: '100%' } }>
|
||||
{ rowRender(rows[virtualRow.index]) }
|
||||
</div>
|
||||
);
|
||||
}) }
|
||||
</Base>
|
||||
</Base>
|
||||
);
|
||||
}
|
@ -12,6 +12,7 @@ export * from './FormGroup';
|
||||
export * from './Grid';
|
||||
export * from './GridContext';
|
||||
export * from './HorizontalRule';
|
||||
export * from './InfiniteScroll';
|
||||
export * from './layout';
|
||||
export * from './Text';
|
||||
export * from './transitions';
|
||||
|
@ -1,8 +1,7 @@
|
||||
import { ILinkEventTracker } from '@nitrots/nitro-renderer';
|
||||
import { FC, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { AutoSizer, CellMeasurer, CellMeasurerCache, List, ListRowProps, ListRowRenderer } from 'react-virtualized';
|
||||
import { FC, useEffect, useMemo, useState } from 'react';
|
||||
import { AddEventLinkTracker, ChatEntryType, LocalizeText, RemoveLinkEventTracker } from '../../api';
|
||||
import { Flex, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../common';
|
||||
import { Flex, InfiniteScroll, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../common';
|
||||
import { useChatHistory } from '../../hooks';
|
||||
|
||||
export const ChatHistoryView: FC<{}> = props =>
|
||||
@ -10,9 +9,6 @@ export const ChatHistoryView: FC<{}> = props =>
|
||||
const [ isVisible, setIsVisible ] = useState(false);
|
||||
const [ searchText, setSearchText ] = useState<string>('');
|
||||
const { chatHistory = [] } = useChatHistory();
|
||||
const elementRef = useRef<List>(null);
|
||||
|
||||
const cache = useMemo(() => new CellMeasurerCache({ defaultHeight: 35, fixedWidth: true }), []);
|
||||
|
||||
const filteredChatHistory = useMemo(() =>
|
||||
{
|
||||
@ -23,10 +19,10 @@ export const ChatHistoryView: FC<{}> = props =>
|
||||
return chatHistory.filter(entry => ((entry.message && entry.message.toLowerCase().includes(text))) || (entry.name && entry.name.toLowerCase().includes(text)));
|
||||
}, [ chatHistory, searchText ]);
|
||||
|
||||
useEffect(() =>
|
||||
/* useEffect(() =>
|
||||
{
|
||||
if(elementRef && elementRef.current && isVisible) elementRef.current.scrollToRow(-1);
|
||||
}, [ isVisible ]);
|
||||
if(elementRef && elementRef.current && isVisible) elementRef.current.scrollTop = elementRef.current.scrollHeight;
|
||||
}, [ isVisible ]); */
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
@ -60,68 +56,39 @@ export const ChatHistoryView: FC<{}> = props =>
|
||||
|
||||
if(!isVisible) return null;
|
||||
|
||||
const RowRenderer: ListRowRenderer = (props: ListRowProps) =>
|
||||
{
|
||||
const item = filteredChatHistory[props.index];
|
||||
|
||||
if (!item) return null;
|
||||
|
||||
return (
|
||||
<CellMeasurer cache={ cache } columnIndex={ 0 } key={ props.key } parent={ props.parent } rowIndex={ props.index }>
|
||||
<Flex alignItems="center" style={ props.style } className="p-1" gap={ 2 }>
|
||||
<Text variant="muted">{ item.timestamp }</Text>
|
||||
{ (item.type === ChatEntryType.TYPE_CHAT) &&
|
||||
<div className="bubble-container" style={ { position: 'relative' } }>
|
||||
{ (item.style === 0) &&
|
||||
<div className="user-container-bg" style={ { backgroundColor: item.color } } /> }
|
||||
<div className={ `chat-bubble bubble-${ item.style } type-${ item.chatType }` } style={ { maxWidth: '100%' } }>
|
||||
<div className="user-container">
|
||||
{ item.imageUrl && (item.imageUrl.length > 0) &&
|
||||
<div className="user-image" style={ { backgroundImage: `url(${ item.imageUrl })` } } /> }
|
||||
</div>
|
||||
<div className="chat-content">
|
||||
<b className="username mr-1" dangerouslySetInnerHTML={ { __html: `${ item.name }: ` } } />
|
||||
<span className="message" dangerouslySetInnerHTML={ { __html: `${ item.message }` } } />
|
||||
</div>
|
||||
</div>
|
||||
</div> }
|
||||
{ (item.type === ChatEntryType.TYPE_ROOM_INFO) &&
|
||||
<>
|
||||
<i className="icon icon-small-room" />
|
||||
<Text textBreak wrap grow>{ item.name }</Text>
|
||||
</> }
|
||||
</Flex>
|
||||
</CellMeasurer>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<NitroCardView uniqueKey="chat-history" className="nitro-chat-history" theme="primary-slim">
|
||||
<NitroCardHeaderView headerText={ LocalizeText('room.chathistory.button.text') } onCloseClick={ event => setIsVisible(false) }/>
|
||||
<NitroCardContentView overflow="hidden">
|
||||
<Flex column fullHeight gap={ 2 }>
|
||||
<input type="text" className="form-control form-control-sm" placeholder={ LocalizeText('generic.search') } value={ searchText } onChange={ event => setSearchText(event.target.value) } />
|
||||
<div className="h-100">
|
||||
<AutoSizer defaultWidth={ 300 } defaultHeight={ 170 }>
|
||||
{ ({ height, width }) =>
|
||||
{
|
||||
cache.clearAll();
|
||||
|
||||
return (
|
||||
<List
|
||||
ref={ elementRef }
|
||||
width={ width }
|
||||
height={ height }
|
||||
rowCount={ filteredChatHistory.length }
|
||||
rowHeight={ 35 }
|
||||
className={ 'chat-history-list' }
|
||||
rowRenderer={ RowRenderer }
|
||||
deferredMeasurementCache={ cache } />
|
||||
)
|
||||
} }
|
||||
</AutoSizer>
|
||||
</div>
|
||||
</Flex>
|
||||
<NitroCardContentView overflow="hidden" gap={ 2 }>
|
||||
<input type="text" className="form-control form-control-sm" placeholder={ LocalizeText('generic.search') } value={ searchText } onChange={ event => setSearchText(event.target.value) } />
|
||||
<InfiniteScroll rows={ filteredChatHistory } estimateSize={ 35 } rowRender={ row =>
|
||||
{
|
||||
return (
|
||||
<Flex alignItems="center" className="p-1" gap={ 2 }>
|
||||
<Text variant="muted">{ row.timestamp }</Text>
|
||||
{ (row.type === ChatEntryType.TYPE_CHAT) &&
|
||||
<div className="bubble-container" style={ { position: 'relative' } }>
|
||||
{ (row.style === 0) &&
|
||||
<div className="user-container-bg" style={ { backgroundColor: row.color } } /> }
|
||||
<div className={ `chat-bubble bubble-${ row.style } type-${ row.chatType }` } style={ { maxWidth: '100%' } }>
|
||||
<div className="user-container">
|
||||
{ row.imageUrl && (row.imageUrl.length > 0) &&
|
||||
<div className="user-image" style={ { backgroundImage: `url(${ row.imageUrl })` } } /> }
|
||||
</div>
|
||||
<div className="chat-content">
|
||||
<b className="username mr-1" dangerouslySetInnerHTML={ { __html: `${ row.name }: ` } } />
|
||||
<span className="message" dangerouslySetInnerHTML={ { __html: `${ row.message }` } } />
|
||||
</div>
|
||||
</div>
|
||||
</div> }
|
||||
{ (row.type === ChatEntryType.TYPE_ROOM_INFO) &&
|
||||
<>
|
||||
<i className="icon icon-small-room" />
|
||||
<Text textBreak wrap grow>{ row.name }</Text>
|
||||
</> }
|
||||
</Flex>
|
||||
)
|
||||
} } />
|
||||
</NitroCardContentView>
|
||||
</NitroCardView>
|
||||
);
|
||||
|
@ -23,12 +23,12 @@
|
||||
.table {
|
||||
color: $black;
|
||||
|
||||
> :not(caption) > * > * {
|
||||
> :not(caption)>*>* {
|
||||
box-shadow: none;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, .2);
|
||||
}
|
||||
|
||||
&.table-striped > tbody > tr:nth-of-type(odd) {
|
||||
&.table-striped>tbody>tr:nth-of-type(odd) {
|
||||
color: $black;
|
||||
background: rgba(0, 0, 0, .05);
|
||||
}
|
||||
@ -41,10 +41,12 @@
|
||||
|
||||
.nitro-mod-tools-chatlog {
|
||||
width: 400px;
|
||||
height: 250px;
|
||||
}
|
||||
|
||||
.nitro-mod-tools-user-visits {
|
||||
width: 250px;
|
||||
height: 250px;
|
||||
}
|
||||
|
||||
.nitro-mod-tools-tickets {
|
||||
@ -59,26 +61,10 @@
|
||||
.nitro-mod-tools-chatlog,
|
||||
.nitro-mod-tools-user-visits {
|
||||
|
||||
.log-container {
|
||||
min-height: 200px;
|
||||
height: 100%;
|
||||
.log-entry {
|
||||
|
||||
.log-entry-container {
|
||||
|
||||
.log-entry {
|
||||
|
||||
&.highlighted {
|
||||
border: 1px solid $red;
|
||||
}
|
||||
}
|
||||
|
||||
&.highlighted {
|
||||
border: 1px solid $red;
|
||||
}
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
padding-top: 0;
|
||||
&.highlighted {
|
||||
border: 1px solid $red;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
11
src/components/mod-tools/views/chatlog/ChatlogRecord.ts
Normal file
11
src/components/mod-tools/views/chatlog/ChatlogRecord.ts
Normal file
@ -0,0 +1,11 @@
|
||||
export interface ChatlogRecord
|
||||
{
|
||||
timestamp?: string;
|
||||
habboId?: number;
|
||||
username?: string;
|
||||
message?: string;
|
||||
hasHighlighting?: boolean;
|
||||
isRoomInfo?: boolean;
|
||||
roomId?: number;
|
||||
roomName?: string;
|
||||
}
|
@ -1,9 +1,9 @@
|
||||
import { ChatRecordData } from '@nitrots/nitro-renderer';
|
||||
import { CSSProperties, FC, Key, useCallback } from 'react';
|
||||
import { AutoSizer, CellMeasurer, CellMeasurerCache, List, ListRowProps } from 'react-virtualized';
|
||||
import { FC, useMemo } from 'react';
|
||||
import { CreateLinkEvent, TryVisitRoom } from '../../../../api';
|
||||
import { Base, Button, Column, Flex, Grid, Text } from '../../../../common';
|
||||
import { Base, Button, Column, Flex, Grid, InfiniteScroll, Text } from '../../../../common';
|
||||
import { useModTools } from '../../../../hooks';
|
||||
import { ChatlogRecord } from './ChatlogRecord';
|
||||
|
||||
interface ChatlogViewProps
|
||||
{
|
||||
@ -15,94 +15,38 @@ export const ChatlogView: FC<ChatlogViewProps> = props =>
|
||||
const { records = null } = props;
|
||||
const { openRoomInfo = null } = useModTools();
|
||||
|
||||
const rowRenderer = (props: ListRowProps) =>
|
||||
const allRecords = useMemo(() =>
|
||||
{
|
||||
let chatlogEntry = records[0].chatlog[props.index];
|
||||
const results: ChatlogRecord[] = [];
|
||||
|
||||
return (
|
||||
<CellMeasurer
|
||||
cache={ cache }
|
||||
columnIndex={ 0 }
|
||||
key={ props.key }
|
||||
parent={ props.parent }
|
||||
rowIndex={ props.index }
|
||||
>
|
||||
<Grid key={ props.key } fullHeight={ false } style={ props.style } gap={ 1 } alignItems="center" className="log-entry py-1 border-bottom">
|
||||
<Text className="g-col-2">{ chatlogEntry.timestamp }</Text>
|
||||
<Text className="g-col-3" bold underline pointer onClick={ event => CreateLinkEvent(`mod-tools/open-user-info/${ chatlogEntry.userId }`) }>{ chatlogEntry.userName }</Text>
|
||||
<Text textBreak wrap className="g-col-7">{ chatlogEntry.message }</Text>
|
||||
</Grid>
|
||||
</CellMeasurer>
|
||||
);
|
||||
};
|
||||
|
||||
const advancedRowRenderer = (props: ListRowProps) =>
|
||||
{
|
||||
let chatlogEntry = null;
|
||||
let currentRecord: ChatRecordData = null;
|
||||
let isRoomInfo = false;
|
||||
let totalIndex = 0;
|
||||
|
||||
for(let i = 0; i < records.length; i++)
|
||||
records.forEach(record =>
|
||||
{
|
||||
currentRecord = records[i];
|
||||
results.push({
|
||||
isRoomInfo: true,
|
||||
roomId: record.roomId,
|
||||
roomName: record.roomName
|
||||
});
|
||||
|
||||
totalIndex++; // row for room info
|
||||
totalIndex = (totalIndex + currentRecord.chatlog.length);
|
||||
|
||||
if(props.index > (totalIndex - 1)) continue;
|
||||
|
||||
if((props.index + 1) === (totalIndex - currentRecord.chatlog.length))
|
||||
record.chatlog.forEach(chatlog =>
|
||||
{
|
||||
isRoomInfo = true;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
const index = (props.index - (totalIndex - currentRecord.chatlog.length));
|
||||
|
||||
chatlogEntry = currentRecord.chatlog[index];
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return (
|
||||
<CellMeasurer
|
||||
cache={ cache }
|
||||
columnIndex={ 0 }
|
||||
key={ props.key }
|
||||
parent={ props.parent }
|
||||
rowIndex={ props.index }
|
||||
>
|
||||
{ (isRoomInfo && currentRecord) &&
|
||||
<RoomInfo roomId={ currentRecord.roomId } roomName={ currentRecord.roomName } uniqueKey={ props.key } style={ props.style } /> }
|
||||
{ !isRoomInfo &&
|
||||
<Grid key={ props.key } fullHeight={ false } style={ props.style } gap={ 1 } alignItems="center" className="log-entry py-1 border-bottom">
|
||||
<Text className="g-col-2">{ chatlogEntry.timestamp }</Text>
|
||||
<Text className="g-col-3" bold underline pointer onClick={ event => CreateLinkEvent(`mod-tools/open-user-info/${ chatlogEntry.userId }`) }>{ chatlogEntry.userName }</Text>
|
||||
<Text textBreak wrap className="g-col-7">{ chatlogEntry.message }</Text>
|
||||
</Grid> }
|
||||
</CellMeasurer>
|
||||
);
|
||||
}
|
||||
|
||||
const getNumRowsForAdvanced = useCallback(() =>
|
||||
{
|
||||
let count = 0;
|
||||
|
||||
for(let i = 0; i < records.length; i++)
|
||||
{
|
||||
count++; // add room info row
|
||||
count = count + records[i].chatlog.length;
|
||||
}
|
||||
|
||||
return count;
|
||||
results.push({
|
||||
timestamp: chatlog.timestamp,
|
||||
habboId: chatlog.userId,
|
||||
username: chatlog.userName,
|
||||
hasHighlighting: chatlog.hasHighlighting,
|
||||
message: chatlog.message,
|
||||
isRoomInfo: false
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return results;
|
||||
}, [ records ]);
|
||||
|
||||
const RoomInfo = (props: { roomId: number, roomName: string, uniqueKey: Key, style: CSSProperties }) =>
|
||||
const RoomInfo = (props: { roomId: number, roomName: string }) =>
|
||||
{
|
||||
return (
|
||||
<Flex key={ props.uniqueKey } gap={ 2 } alignItems="center" justifyContent="between" className="room-info bg-muted rounded p-1" style={ props.style }>
|
||||
<Flex gap={ 2 } alignItems="center" justifyContent="between" className="bg-muted rounded p-1">
|
||||
<Flex gap={ 1 }>
|
||||
<Text bold>Room name:</Text>
|
||||
<Text>{ props.roomName }</Text>
|
||||
@ -115,15 +59,8 @@ export const ChatlogView: FC<ChatlogViewProps> = props =>
|
||||
);
|
||||
}
|
||||
|
||||
const cache = new CellMeasurerCache({
|
||||
defaultHeight: 25,
|
||||
fixedWidth: true
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
{ (records && (records.length === 1)) &&
|
||||
<RoomInfo roomId={ records[0].roomId } roomName={ records[0].roomName } uniqueKey={ null } style={ {} } /> }
|
||||
<Column fit gap={ 0 } overflow="hidden">
|
||||
<Column gap={ 2 }>
|
||||
<Grid gap={ 1 } className="text-black fw-bold border-bottom pb-1">
|
||||
@ -133,25 +70,21 @@ export const ChatlogView: FC<ChatlogViewProps> = props =>
|
||||
</Grid>
|
||||
</Column>
|
||||
{ (records && (records.length > 0)) &&
|
||||
<Column className="log-container striped-children" overflow="auto" gap={ 0 }>
|
||||
<AutoSizer defaultWidth={ 400 } defaultHeight={ 200 }>
|
||||
{ ({ height, width }) =>
|
||||
{
|
||||
cache.clearAll();
|
||||
|
||||
return (
|
||||
<List
|
||||
width={ width }
|
||||
height={ height }
|
||||
rowCount={ (records.length > 1) ? getNumRowsForAdvanced() : records[0].chatlog.length }
|
||||
rowHeight={ cache.rowHeight }
|
||||
className={ 'log-entry-container' }
|
||||
rowRenderer={ (records.length > 1) ? advancedRowRenderer : rowRenderer }
|
||||
deferredMeasurementCache={ cache } />
|
||||
);
|
||||
} }
|
||||
</AutoSizer>
|
||||
</Column> }
|
||||
<InfiniteScroll rows={ allRecords } estimateSize={ 25 } rowRender={ (row: ChatlogRecord) =>
|
||||
{
|
||||
return (
|
||||
<>
|
||||
{ row.isRoomInfo &&
|
||||
<RoomInfo roomId={ row.roomId } roomName={ row.roomName } /> }
|
||||
{ !row.isRoomInfo &&
|
||||
<Grid fullHeight={ false } gap={ 1 } alignItems="center" className="log-entry py-1 border-bottom">
|
||||
<Text className="g-col-2">{ row.timestamp }</Text>
|
||||
<Text className="g-col-3" bold underline pointer onClick={ event => CreateLinkEvent(`mod-tools/open-user-info/${ row.habboId }`) }>{ row.username }</Text>
|
||||
<Text textBreak wrap className="g-col-7">{ row.message }</Text>
|
||||
</Grid> }
|
||||
</>
|
||||
);
|
||||
} } /> }
|
||||
</Column>
|
||||
</>
|
||||
);
|
||||
|
@ -35,7 +35,7 @@ export const ModToolsChatlogView: FC<ModToolsChatlogViewProps> = props =>
|
||||
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 h-100">
|
||||
<NitroCardContentView className="text-black" overflow="hidden">
|
||||
{ roomChatlog &&
|
||||
<ChatlogView records={ [ roomChatlog ] } /> }
|
||||
</NitroCardContentView>
|
||||
|
@ -1,8 +1,7 @@
|
||||
import { GetRoomVisitsMessageComposer, RoomVisitsData, RoomVisitsEvent } from '@nitrots/nitro-renderer';
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import { AutoSizer, List, ListRowProps } from 'react-virtualized';
|
||||
import { SendMessageComposer, TryVisitRoom } from '../../../../api';
|
||||
import { Base, Column, DraggableWindowPosition, Grid, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../../common';
|
||||
import { Base, Column, DraggableWindowPosition, Grid, InfiniteScroll, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../../common';
|
||||
import { useMessageEvent } from '../../../../hooks';
|
||||
|
||||
interface ModToolsUserRoomVisitsViewProps
|
||||
@ -32,19 +31,6 @@ export const ModToolsUserRoomVisitsView: FC<ModToolsUserRoomVisitsViewProps> = p
|
||||
|
||||
if(!userId) return null;
|
||||
|
||||
const RowRenderer = (props: ListRowProps) =>
|
||||
{
|
||||
const item = roomVisitData.rooms[props.index];
|
||||
|
||||
return (
|
||||
<Grid key={ props.key } fullHeight={ false } style={ props.style } gap={ 1 } alignItems="center" className="text-black py-1 border-bottom">
|
||||
<Text className="g-col-2">{ item.enterHour.toString().padStart(2, '0') }: { item.enterMinute.toString().padStart(2, '0') }</Text>
|
||||
<Text className="g-col-7">{ item.roomName }</Text>
|
||||
<Text bold underline pointer variant="primary" className="g-col-3" onClick={ event => TryVisitRoom(item.roomId) }>Visit Room</Text>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<NitroCardView className="nitro-mod-tools-user-visits" theme="primary-slim" windowPosition={ DraggableWindowPosition.TOP_LEFT }>
|
||||
<NitroCardHeaderView headerText={ 'User Visits' } onCloseClick={ onCloseClick } />
|
||||
@ -57,24 +43,16 @@ export const ModToolsUserRoomVisitsView: FC<ModToolsUserRoomVisitsViewProps> = p
|
||||
<Base className="g-col-3">Visit</Base>
|
||||
</Grid>
|
||||
</Column>
|
||||
<Column className="log-container striped-children" overflow="auto" gap={ 0 }>
|
||||
{ roomVisitData &&
|
||||
<AutoSizer defaultWidth={ 400 } defaultHeight={ 200 }>
|
||||
{ ({ height, width }) =>
|
||||
{
|
||||
return (
|
||||
<List
|
||||
width={ width }
|
||||
height={ height }
|
||||
rowCount={ roomVisitData.rooms.length }
|
||||
rowHeight={ 25 }
|
||||
className={ 'log-entry-container' }
|
||||
rowRenderer={ RowRenderer }
|
||||
/>
|
||||
);
|
||||
} }
|
||||
</AutoSizer> }
|
||||
</Column>
|
||||
<InfiniteScroll rows={ roomVisitData?.rooms ?? [] } estimateSize={ 25 } rowRender={ row =>
|
||||
{
|
||||
return (
|
||||
<Grid fullHeight={ false } gap={ 1 } alignItems="center" className="text-black py-1 border-bottom">
|
||||
<Text className="g-col-2">{ row.enterHour.toString().padStart(2, '0') }: { row.enterMinute.toString().padStart(2, '0') }</Text>
|
||||
<Text className="g-col-7">{ row.roomName }</Text>
|
||||
<Text bold underline pointer variant="primary" className="g-col-3" onClick={ event => TryVisitRoom(row.roomId) }>Visit Room</Text>
|
||||
</Grid>
|
||||
);
|
||||
} } />
|
||||
</Column>
|
||||
</NitroCardContentView>
|
||||
</NitroCardView>
|
||||
|
@ -1,7 +1,7 @@
|
||||
import classNames from 'classnames';
|
||||
import { FC, useEffect, useMemo, useState } from 'react';
|
||||
import { AutoSizer, List, ListRowProps, ListRowRenderer } from 'react-virtualized';
|
||||
import { GetSessionDataManager, LocalizeText, RoomObjectItem } from '../../../../api';
|
||||
import { Column, Flex, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../../common';
|
||||
import { Flex, InfiniteScroll, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../../common';
|
||||
|
||||
interface ChooserWidgetViewProps
|
||||
{
|
||||
@ -25,17 +25,6 @@ export const ChooserWidgetView: FC<ChooserWidgetViewProps> = props =>
|
||||
return items.filter(item => item.name.toLocaleLowerCase().includes(value));
|
||||
}, [ items, searchValue ]);
|
||||
|
||||
const rowRenderer: ListRowRenderer = (props: ListRowProps) =>
|
||||
{
|
||||
const item = filteredItems[props.index];
|
||||
|
||||
return (
|
||||
<Flex key={ props.key } alignItems="center" position="absolute" className={ 'rounded px-1' + ((selectedItem === item) ? ' bg-muted' : '') } pointer style={ props.style } onClick={ event => setSelectedItem(item) }>
|
||||
<Text truncate>{ item.name } { canSeeId && (' - ' + item.id) }</Text>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!selectedItem) return;
|
||||
@ -46,21 +35,16 @@ export const ChooserWidgetView: FC<ChooserWidgetViewProps> = props =>
|
||||
return (
|
||||
<NitroCardView className="nitro-chooser-widget" theme="primary-slim">
|
||||
<NitroCardHeaderView headerText={ title } onCloseClick={ onClose } />
|
||||
<NitroCardContentView overflow="hidden">
|
||||
<NitroCardContentView overflow="hidden" gap={ 2 }>
|
||||
<input type="text" className="form-control form-control-sm" placeholder={ LocalizeText('generic.search') } value={ searchValue } onChange={ event => setSearchValue(event.target.value) } />
|
||||
<Column fullHeight overflow="auto">
|
||||
<AutoSizer defaultWidth={ 0 } defaultHeight={ 0 }>
|
||||
{ ({ width, height }) =>
|
||||
{
|
||||
return (<List
|
||||
width={ width }
|
||||
height={ height }
|
||||
rowCount={ filteredItems.length }
|
||||
rowHeight={ 20 }
|
||||
rowRenderer={ rowRenderer } />)
|
||||
} }
|
||||
</AutoSizer>
|
||||
</Column>
|
||||
<InfiniteScroll rows={ filteredItems } estimateSize={ 25 } rowRender={ row =>
|
||||
{
|
||||
return (
|
||||
<Flex alignItems="center" className={ classNames('rounded p-1', (selectedItem === row) && 'bg-muted') } pointer onClick={ event => setSelectedItem(row) }>
|
||||
<Text truncate>{ row.name } { canSeeId && (' - ' + row.id) }</Text>
|
||||
</Flex>
|
||||
);
|
||||
} } />
|
||||
</NitroCardContentView>
|
||||
</NitroCardView>
|
||||
);
|
||||
|
51
yarn.lock
51
yarn.lock
@ -1043,7 +1043,7 @@
|
||||
core-js-pure "^3.20.2"
|
||||
regenerator-runtime "^0.13.4"
|
||||
|
||||
"@babel/runtime@^7.10.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.16.3", "@babel/runtime@^7.17.2", "@babel/runtime@^7.18.3", "@babel/runtime@^7.18.9", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.2", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7":
|
||||
"@babel/runtime@^7.10.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.16.3", "@babel/runtime@^7.17.2", "@babel/runtime@^7.18.3", "@babel/runtime@^7.18.9", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.2", "@babel/runtime@^7.6.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7":
|
||||
version "7.18.9"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.18.9.tgz#b4fcfce55db3d2e5e080d2490f608a3b9f407f4a"
|
||||
integrity sha512-lkqXDcvlFT5rvEjiu6+QYO+1GXrEHRo2LOtS7E4GtX5ESIZOgepqsZBVIj6Pv+a6zqsya9VCgiK1KAK4BvJDAw==
|
||||
@ -1614,9 +1614,9 @@
|
||||
integrity sha512-4eMqkns+NL2/DmdezjbVG4TW+eII3hvgDM3koDQNoO4yjMgU+55TTptPU9jJL/JJwntRiUECLSIHg8eZxmA5mA==
|
||||
|
||||
"@pixi/filter-adjustment@^4.1.3":
|
||||
version "4.1.3"
|
||||
resolved "https://registry.yarnpkg.com/@pixi/filter-adjustment/-/filter-adjustment-4.1.3.tgz#61e34b4dd9766ccf40463f0538201bf68f78df66"
|
||||
integrity sha512-W+NhPiZRYKoRToa5+tkU95eOw8gnS5dfIp3ZP+pLv2mdER9RI+4xHxp1uLHMqUYZViTaMdZIIoVOuCgHFPYCbQ==
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@pixi/filter-adjustment/-/filter-adjustment-4.2.0.tgz#1648f705f856619835184dae42299138bf964c83"
|
||||
integrity sha512-3Pvo5WyUb8X5GQ69X3/Tj6rHl0q7gArvcIYKeV2+/PzHaMypVF4Lsr3I3zYSyWWokxOQ0bFCAtNWm1WwDRA41g==
|
||||
|
||||
"@pixi/filter-alpha@~6.4.2":
|
||||
version "6.4.2"
|
||||
@ -1932,6 +1932,18 @@
|
||||
"@svgr/plugin-svgo" "^5.5.0"
|
||||
loader-utils "^2.0.0"
|
||||
|
||||
"@tanstack/react-virtual@^3.0.0-beta.18":
|
||||
version "3.0.0-beta.18"
|
||||
resolved "https://registry.yarnpkg.com/@tanstack/react-virtual/-/react-virtual-3.0.0-beta.18.tgz#b97b2019f7d6a5770fb88ee1f7591da55b9059b4"
|
||||
integrity sha512-mnyCZT6htcRNw1jVb+WyfMUMbd1UmXX/JWPuMf6Bmj92DB/V7Ogk5n5rby5Y5aste7c7mlsBeMF8HtpwERRvEQ==
|
||||
dependencies:
|
||||
"@tanstack/virtual-core" "3.0.0-beta.18"
|
||||
|
||||
"@tanstack/virtual-core@3.0.0-beta.18":
|
||||
version "3.0.0-beta.18"
|
||||
resolved "https://registry.yarnpkg.com/@tanstack/virtual-core/-/virtual-core-3.0.0-beta.18.tgz#d4b0738c1d0aada922063c899675ff4df9f696b2"
|
||||
integrity sha512-tcXutY05NpN9lp3+AXI9Sn85RxSPV0EJC0XMim9oeQj/E7bjXoL0qZ4Er4wwnvIbv/hZjC91EmbIQGjgdr6nZg==
|
||||
|
||||
"@tootallnate/once@1":
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82"
|
||||
@ -2123,15 +2135,7 @@
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/react-virtualized@^9.21.21":
|
||||
version "9.21.21"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-virtualized/-/react-virtualized-9.21.21.tgz#65c96f25314f0fb3d40536929dc78112753b49e1"
|
||||
integrity sha512-Exx6I7p4Qn+BBA1SRyj/UwQlZ0I0Pq7g7uhAp0QQ4JWzZunqEqNBGTmCmMmS/3N9wFgAGWuBD16ap7k8Y14VPA==
|
||||
dependencies:
|
||||
"@types/prop-types" "*"
|
||||
"@types/react" "^17"
|
||||
|
||||
"@types/react@*", "@types/react@>=16.9.11", "@types/react@^17", "@types/react@^18.0.15":
|
||||
"@types/react@*", "@types/react@>=16.9.11", "@types/react@^18.0.15":
|
||||
version "18.0.15"
|
||||
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.15.tgz#d355644c26832dc27f3e6cbf0c4f4603fc4ab7fe"
|
||||
integrity sha512-iz3BtLuIYH1uWdsv6wXYdhozhqj20oD4/Hk2DNXIn1kFsmp9x8d9QB6FnPhfkbhd2PgEONt9Q1x/ebkwjfFLow==
|
||||
@ -3715,11 +3719,6 @@ cliui@^6.0.0:
|
||||
strip-ansi "^6.0.0"
|
||||
wrap-ansi "^6.2.0"
|
||||
|
||||
clsx@^1.0.4:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12"
|
||||
integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==
|
||||
|
||||
co@^4.6.0:
|
||||
version "4.6.0"
|
||||
resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
|
||||
@ -4567,7 +4566,7 @@ dom-converter@^0.2.0:
|
||||
dependencies:
|
||||
utila "~0.4"
|
||||
|
||||
dom-helpers@^5.0.1, dom-helpers@^5.1.3, dom-helpers@^5.2.0, dom-helpers@^5.2.1:
|
||||
dom-helpers@^5.0.1, dom-helpers@^5.2.0, dom-helpers@^5.2.1:
|
||||
version "5.2.1"
|
||||
resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902"
|
||||
integrity sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==
|
||||
@ -9420,7 +9419,7 @@ prop-types@15.7.2:
|
||||
object-assign "^4.1.1"
|
||||
react-is "^16.8.1"
|
||||
|
||||
prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1:
|
||||
prop-types@^15.6.2, prop-types@^15.8.1:
|
||||
version "15.8.1"
|
||||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
|
||||
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
|
||||
@ -9757,18 +9756,6 @@ react-transition-group@^4.4.2:
|
||||
loose-envify "^1.4.0"
|
||||
prop-types "^15.6.2"
|
||||
|
||||
react-virtualized@^9.22.3:
|
||||
version "9.22.3"
|
||||
resolved "https://registry.yarnpkg.com/react-virtualized/-/react-virtualized-9.22.3.tgz#f430f16beb0a42db420dbd4d340403c0de334421"
|
||||
integrity sha512-MKovKMxWTcwPSxE1kK1HcheQTWfuCxAuBoSTf2gwyMM21NdX/PXUhnoP8Uc5dRKd+nKm8v41R36OellhdCpkrw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.7.2"
|
||||
clsx "^1.0.4"
|
||||
dom-helpers "^5.1.3"
|
||||
loose-envify "^1.4.0"
|
||||
prop-types "^15.7.2"
|
||||
react-lifecycles-compat "^3.0.4"
|
||||
|
||||
react-youtube@^7.13.1:
|
||||
version "7.14.0"
|
||||
resolved "https://registry.yarnpkg.com/react-youtube/-/react-youtube-7.14.0.tgz#0505d86491521ca94ef0afb74af3f7936dc7bc86"
|
||||
|
Loading…
Reference in New Issue
Block a user