Skip to content

Commit d893bb3

Browse files
poc: Virtua message list
remove shift
1 parent eb78ae4 commit d893bb3

7 files changed

Lines changed: 160 additions & 59 deletions

File tree

apps/meteor/client/views/room/MessageList/MessageList.tsx

Lines changed: 61 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
import type { IRoom } from '@rocket.chat/core-typings';
1+
import type { IRoom, IUser } from '@rocket.chat/core-typings';
22
import { isThreadMessage } from '@rocket.chat/core-typings';
33
import { MessageTypes } from '@rocket.chat/message-types';
44
import { useSetting, useUserPreference } from '@rocket.chat/ui-contexts';
55
import type { ComponentProps } from 'react';
66
import { Fragment } from 'react';
7+
import { useTranslation } from 'react-i18next';
8+
import { VList } from 'virtua';
79

810
import { MessageListItem } from './MessageListItem';
911
import { useRoomSubscription } from '../contexts/RoomContext';
@@ -12,43 +14,79 @@ import { SelectedMessagesProvider } from '../providers/SelectedMessagesProvider'
1214
import { useMessages } from './hooks/useMessages';
1315
import { isMessageSequential } from './lib/isMessageSequential';
1416
import MessageListProvider from './providers/MessageListProvider';
17+
import LoadingMessagesIndicator from '../body/LoadingMessagesIndicator';
18+
import RetentionPolicyWarning from '../body/RetentionPolicyWarning';
19+
import RoomForeword from '../body/RoomForeword/RoomForeword';
20+
import type { RetentionPolicy } from '../hooks/useRetentionPolicy';
1521

1622
type MessageListProps = {
1723
rid: IRoom['_id'];
1824
messageListRef: ComponentProps<typeof MessageListProvider>['messageListRef'];
25+
canPreview: boolean;
26+
hasMorePreviousMessages: boolean;
27+
isLoadingMoreMessages: boolean;
28+
user: IUser | null;
29+
room: IRoom;
30+
retentionPolicy: RetentionPolicy | undefined;
31+
hasMoreNextMessages: boolean;
1932
};
2033

21-
export const MessageList = function MessageList({ rid, messageListRef }: MessageListProps) {
34+
export const MessageList = function MessageList({
35+
rid,
36+
messageListRef,
37+
canPreview,
38+
hasMorePreviousMessages,
39+
isLoadingMoreMessages,
40+
user,
41+
room,
42+
retentionPolicy,
43+
hasMoreNextMessages,
44+
}: MessageListProps) {
2245
const messages = useMessages({ rid });
2346
const subscription = useRoomSubscription();
2447
const showUserAvatar = !!useUserPreference<boolean>('displayAvatars');
2548
const messageGroupingPeriod = useSetting('Message_GroupingPeriod', 300);
2649
const firstUnreadMessageId = useFirstUnreadMessageId();
27-
50+
const { t } = useTranslation();
2851
return (
2952
<MessageListProvider messageListRef={messageListRef}>
3053
<SelectedMessagesProvider>
31-
{messages.map((message, index, { [index - 1]: previous }) => {
32-
const sequential = isMessageSequential(message, previous, messageGroupingPeriod);
33-
const showUnreadDivider = firstUnreadMessageId === message._id;
34-
const system = MessageTypes.isSystemMessage(message);
35-
const visible = !isThreadMessage(message) && !system;
54+
<VList shift style={{ height: '100%' }} aria-label={t('Message_list')} aria-busy={isLoadingMoreMessages}>
55+
{canPreview ? (
56+
<>
57+
{hasMorePreviousMessages ? (
58+
<li className='load-more'>{isLoadingMoreMessages ? <LoadingMessagesIndicator /> : null}</li>
59+
) : (
60+
<li>
61+
<RoomForeword user={user} room={room} />
62+
{retentionPolicy?.isActive ? <RetentionPolicyWarning room={room} /> : null}
63+
</li>
64+
)}
65+
</>
66+
) : null}
67+
{messages.map((message, index, { [index - 1]: previous }) => {
68+
const sequential = isMessageSequential(message, previous, messageGroupingPeriod);
69+
const showUnreadDivider = firstUnreadMessageId === message._id;
70+
const system = MessageTypes.isSystemMessage(message);
71+
const visible = !isThreadMessage(message) && !system;
3672

37-
return (
38-
<Fragment key={message._id}>
39-
<MessageListItem
40-
message={message}
41-
previous={previous}
42-
showUnreadDivider={showUnreadDivider}
43-
showUserAvatar={showUserAvatar}
44-
sequential={sequential}
45-
visible={visible}
46-
subscription={subscription}
47-
system={system}
48-
/>
49-
</Fragment>
50-
);
51-
})}
73+
return (
74+
<Fragment key={message._id}>
75+
<MessageListItem
76+
message={message}
77+
previous={previous}
78+
showUnreadDivider={showUnreadDivider}
79+
showUserAvatar={showUserAvatar}
80+
sequential={sequential}
81+
visible={visible}
82+
subscription={subscription}
83+
system={system}
84+
/>
85+
</Fragment>
86+
);
87+
})}
88+
{hasMoreNextMessages ? <li className='load-more'>{isLoadingMoreMessages ? <LoadingMessagesIndicator /> : null}</li> : null}
89+
</VList>
5290
</SelectedMessagesProvider>
5391
</MessageListProvider>
5492
);

apps/meteor/client/views/room/body/RoomBody.tsx

Lines changed: 14 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Box } from '@rocket.chat/fuselage';
22
import { isTruthy } from '@rocket.chat/tools';
3-
import { CustomScrollbars, useEmbeddedLayout } from '@rocket.chat/ui-client';
3+
import { CustomVirtuaScrollbars, useEmbeddedLayout } from '@rocket.chat/ui-client';
44
import { usePermission, useRole, useSetting, useTranslation, useUser, useUserPreference, useRoomToolbox } from '@rocket.chat/ui-contexts';
55
import type { MouseEvent, ReactElement } from 'react';
66
import { memo, useCallback, useMemo } from 'react';
@@ -10,9 +10,6 @@ import { BubbleDate } from '../BubbleDate';
1010
import { MessageList } from '../MessageList';
1111
import DropTargetOverlay from './DropTargetOverlay';
1212
import JumpToRecentMessageButton from './JumpToRecentMessageButton';
13-
import LoadingMessagesIndicator from './LoadingMessagesIndicator';
14-
import RetentionPolicyWarning from './RetentionPolicyWarning';
15-
import RoomForeword from './RoomForeword/RoomForeword';
1613
import UnreadMessagesIndicator from './UnreadMessagesIndicator';
1714
import { useRestoreScrollPosition } from './hooks/useRestoreScrollPosition';
1815
import MessageListErrorBoundary from '../MessageList/MessageListErrorBoundary';
@@ -233,26 +230,19 @@ const RoomBody = (): ReactElement => {
233230
.join(' ')}
234231
>
235232
<MessageListErrorBoundary>
236-
<CustomScrollbars ref={innerRef} key={room._id}>
237-
<ul className='messages-list' aria-label={t('Message_list')} aria-busy={isLoadingMoreMessages}>
238-
{canPreview ? (
239-
<>
240-
{hasMorePreviousMessages ? (
241-
<li className='load-more'>{isLoadingMoreMessages ? <LoadingMessagesIndicator /> : null}</li>
242-
) : (
243-
<li>
244-
<RoomForeword user={user} room={room} />
245-
{retentionPolicy?.isActive ? <RetentionPolicyWarning room={room} /> : null}
246-
</li>
247-
)}
248-
</>
249-
) : null}
250-
<MessageList rid={room._id} messageListRef={jumpToRef} />
251-
{hasMoreNextMessages ? (
252-
<li className='load-more'>{isLoadingMoreMessages ? <LoadingMessagesIndicator /> : null}</li>
253-
) : null}
254-
</ul>
255-
</CustomScrollbars>
233+
<CustomVirtuaScrollbars ref={innerRef} key={room._id}>
234+
<MessageList
235+
rid={room._id}
236+
messageListRef={jumpToRef}
237+
canPreview={canPreview}
238+
hasMorePreviousMessages={hasMorePreviousMessages}
239+
isLoadingMoreMessages={isLoadingMoreMessages}
240+
user={user}
241+
room={room}
242+
retentionPolicy={retentionPolicy}
243+
hasMoreNextMessages={hasMoreNextMessages}
244+
/>
245+
</CustomVirtuaScrollbars>
256246
</MessageListErrorBoundary>
257247
</div>
258248
</div>

apps/meteor/client/views/room/hooks/useRetentionPolicy.ts

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -84,18 +84,16 @@ const getMaxAge = (room: IRoom, { maxAgeChannels, maxAgeGroups, maxAgeDMs }: Ret
8484
return -Infinity;
8585
};
8686

87-
export const useRetentionPolicy = (
88-
room: IRoom | undefined,
89-
):
90-
| {
91-
enabled: boolean;
92-
isActive: boolean;
93-
filesOnly: boolean;
94-
excludePinned: boolean;
95-
ignoreThreads: boolean;
96-
maxAge: number;
97-
}
98-
| undefined => {
87+
export type RetentionPolicy = {
88+
enabled: boolean;
89+
isActive: boolean;
90+
filesOnly: boolean;
91+
excludePinned: boolean;
92+
ignoreThreads: boolean;
93+
maxAge: number;
94+
};
95+
96+
export const useRetentionPolicy = (room: IRoom | undefined): RetentionPolicy | undefined => {
9997
const settings = {
10098
enabled: useSetting('RetentionPolicy_Enabled', false),
10199
filesOnly: useSetting('RetentionPolicy_FilesOnly', false),

apps/meteor/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,7 @@
302302
"ua-parser-js": "~1.0.41",
303303
"underscore": "^1.13.7",
304304
"universal-perf-hooks": "^1.0.1",
305+
"virtua": "^0.49.0",
305306
"webdav": "^4.11.5",
306307
"xml-crypto": "~3.2.1",
307308
"xml-encryption": "~3.1.0",
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { useOverlayScrollbars } from 'overlayscrollbars-react';
2+
import type { HTMLAttributes, ReactElement } from 'react';
3+
import { useEffect, memo, forwardRef, useRef } from 'react';
4+
5+
import BaseScrollbars from './BaseScrollbars';
6+
7+
type CustomScrollbarsProps = {
8+
children: ReactElement;
9+
} & Omit<HTMLAttributes<HTMLDivElement>, 'is' | 'onScroll'>;
10+
11+
const CustomVirtuaScrollbars = forwardRef<HTMLElement, CustomScrollbarsProps>(function CustomScrollbars({ ...props }, ref) {
12+
const rootRef = useRef<HTMLElement | null>(null);
13+
14+
const [initialize] = useOverlayScrollbars({
15+
defer: true,
16+
events: {
17+
initialized(osInstance) {
18+
// force overflow styles
19+
const { viewport } = osInstance.elements();
20+
viewport.style.overflowX = `var(--os-viewport-overflow-x)`;
21+
viewport.style.overflowY = `var(--os-viewport-overflow-y)`;
22+
23+
if (typeof ref === 'function') {
24+
ref(viewport);
25+
} else if (ref) {
26+
ref.current = viewport;
27+
}
28+
},
29+
},
30+
});
31+
32+
useEffect(() => {
33+
const { current: root } = rootRef;
34+
35+
if (root?.firstElementChild && root.firstElementChild instanceof HTMLElement) {
36+
initialize({
37+
target: root,
38+
elements: {
39+
viewport: root.firstElementChild,
40+
},
41+
});
42+
}
43+
}, [initialize]);
44+
45+
return <BaseScrollbars ref={rootRef} {...props} />;
46+
});
47+
48+
export default memo(CustomVirtuaScrollbars);

packages/ui-client/src/components/CustomScrollbars/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ import { OverlayScrollbars } from 'overlayscrollbars';
33
export { OverlayScrollbars };
44
export { default as CustomScrollbars } from './CustomScrollbars';
55
export { default as VirtualizedScrollbars } from './VirtualizedScrollbars';
6+
export { default as CustomVirtuaScrollbars } from './CustomVirtuaScrollbars';

yarn.lock

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10331,6 +10331,7 @@ __metadata:
1033110331
ua-parser-js: "npm:~1.0.41"
1033210332
underscore: "npm:^1.13.7"
1033310333
universal-perf-hooks: "npm:^1.0.1"
10334+
virtua: "npm:^0.49.0"
1033410335
webdav: "npm:^4.11.5"
1033510336
webpack: "npm:~5.99.9"
1033610337
xml-crypto: "npm:~3.2.1"
@@ -37558,6 +37559,30 @@ __metadata:
3755837559
languageName: node
3755937560
linkType: hard
3756037561

37562+
"virtua@npm:^0.49.0":
37563+
version: 0.49.0
37564+
resolution: "virtua@npm:0.49.0"
37565+
peerDependencies:
37566+
react: ">=16.14.0"
37567+
react-dom: ">=16.14.0"
37568+
solid-js: ">=1.0"
37569+
svelte: ">=5.0"
37570+
vue: ">=3.2"
37571+
peerDependenciesMeta:
37572+
react:
37573+
optional: true
37574+
react-dom:
37575+
optional: true
37576+
solid-js:
37577+
optional: true
37578+
svelte:
37579+
optional: true
37580+
vue:
37581+
optional: true
37582+
checksum: 10/fca615a4aaf5c7b95e969bfdd25ec68f61713d5af23fb4276fb05282bde9194c1cbd808bcfe68381f659c5927168f03f6654f3b8eeaab7acd92519b8976311b6
37583+
languageName: node
37584+
linkType: hard
37585+
3756137586
"vite-compatible-readable-stream@npm:^3.6.1":
3756237587
version: 3.6.1
3756337588
resolution: "vite-compatible-readable-stream@npm:3.6.1"

0 commit comments

Comments
 (0)