Skip to content

Commit 7af0a7d

Browse files
Initial commit
1 parent 1a75c2a commit 7af0a7d

File tree

5 files changed

+152
-29
lines changed

5 files changed

+152
-29
lines changed

src/components/ChannelList/ChannelList.tsx

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { useNotificationMessageNewListener } from './hooks/useNotificationMessag
1515
import { useNotificationRemovedFromChannelListener } from './hooks/useNotificationRemovedFromChannelListener';
1616
import { CustomQueryChannelsFn, usePaginatedChannels } from './hooks/usePaginatedChannels';
1717
import { useUserPresenceChangedListener } from './hooks/useUserPresenceChangedListener';
18-
import { MAX_QUERY_CHANNELS_LIMIT, moveChannelUp } from './utils';
18+
import { MAX_QUERY_CHANNELS_LIMIT, moveChannelUpwards } from './utils';
1919

2020
import { AvatarProps, Avatar as DefaultAvatar } from '../Avatar/Avatar';
2121
import { ChannelPreview, ChannelPreviewUIComponentProps } from '../ChannelPreview/ChannelPreview';
@@ -62,6 +62,7 @@ export type ChannelListProps<
6262
) => Array<Channel<StreamChatGenerics>>;
6363
/** Custom UI component to display search results, defaults to and accepts same props as: [ChannelSearch](https://github.com/GetStream/stream-chat-react/blob/master/src/components/ChannelSearch/ChannelSearch.tsx) */
6464
ChannelSearch?: React.ComponentType<ChannelSearchProps<StreamChatGenerics>>;
65+
// FIXME: how is this even legal (WHY IS IT STRING?!)
6566
/** Set a channel (with this ID) to active and manually move it to the top of the list */
6667
customActiveChannel?: string;
6768
/** Custom function that handles the channel pagination. Has to build query filters, sort and options and query and append channels to the current channels state and update the hasNext pagination flag after each query. */
@@ -228,6 +229,7 @@ const UnMemoizedChannelList = <
228229
}
229230

230231
if (customActiveChannel) {
232+
// FIXME: this is wrong...
231233
let customActiveChannelObject = channels.find((chan) => chan.id === customActiveChannel);
232234

233235
if (!customActiveChannelObject) {
@@ -238,10 +240,12 @@ const UnMemoizedChannelList = <
238240
if (customActiveChannelObject) {
239241
setActiveChannel(customActiveChannelObject, watchers);
240242

241-
const newChannels = moveChannelUp({
242-
activeChannel: customActiveChannelObject,
243+
const newChannels = moveChannelUpwards({
243244
channels,
244-
cid: customActiveChannelObject.cid,
245+
channelToMove: customActiveChannelObject,
246+
// TODO: adjust acordingly (based on sort)
247+
considerPinnedChannels: false,
248+
userId: client.userID!,
245249
});
246250

247251
setChannels(newChannels);
@@ -298,6 +302,8 @@ const UnMemoizedChannelList = <
298302
onMessageNewHandler,
299303
lockChannelOrder,
300304
allowNewMessagesFromUnfilteredChannels,
305+
// TODO: adjust accordingly (consider sort option)
306+
false,
301307
);
302308
useNotificationMessageNewListener(
303309
setChannels,

src/components/ChannelList/__tests__/ChannelList.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import {
2626
queryChannelsApi,
2727
queryUsersApi,
2828
useMockedApis,
29-
} from 'mock-builders';
29+
} from '../../../mock-builders';
3030

3131
import { Chat } from '../../Chat';
3232
import { ChannelList } from '../ChannelList';

src/components/ChannelList/hooks/useMessageNewListener.ts

Lines changed: 57 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,82 @@
11
import { useEffect } from 'react';
2-
import uniqBy from 'lodash.uniqby';
3-
4-
import { moveChannelUp } from '../utils';
2+
import type { Dispatch, SetStateAction } from 'react';
53

64
import { useChatContext } from '../../../context/ChatContext';
75

8-
import type { Channel, Event } from 'stream-chat';
6+
import type { Channel, Event, ExtendableGenerics } from 'stream-chat';
97

108
import type { DefaultStreamChatGenerics } from '../../../types/types';
9+
import { moveChannelUpwards } from '../utils';
10+
11+
export const isChannelPinned = <SCG extends ExtendableGenerics>({
12+
channel,
13+
userId,
14+
}: {
15+
userId: string;
16+
channel?: Channel<SCG>;
17+
}) => {
18+
if (!channel) return false;
19+
20+
const member = channel.state.members[userId];
21+
22+
return !!member?.pinned_at;
23+
};
1124

1225
export const useMessageNewListener = <
13-
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics
26+
SCG extends DefaultStreamChatGenerics = DefaultStreamChatGenerics
1427
>(
15-
setChannels: React.Dispatch<React.SetStateAction<Array<Channel<StreamChatGenerics>>>>,
28+
setChannels: Dispatch<SetStateAction<Array<Channel<SCG>>>>,
1629
customHandler?: (
17-
setChannels: React.Dispatch<React.SetStateAction<Array<Channel<StreamChatGenerics>>>>,
18-
event: Event<StreamChatGenerics>,
30+
setChannels: Dispatch<SetStateAction<Array<Channel<SCG>>>>,
31+
event: Event<SCG>,
1932
) => void,
2033
lockChannelOrder = false,
2134
allowNewMessagesFromUnfilteredChannels = true,
35+
considerPinnedChannels = false, // automatically set to true by checking sorting options (must include {pinned_at: -1/1})
2236
) => {
23-
const { client } = useChatContext<StreamChatGenerics>('useMessageNewListener');
37+
const { client } = useChatContext<SCG>('useMessageNewListener');
2438

2539
useEffect(() => {
26-
const handleEvent = (event: Event<StreamChatGenerics>) => {
40+
const handleEvent = (event: Event<SCG>) => {
2741
if (customHandler && typeof customHandler === 'function') {
2842
customHandler(setChannels, event);
2943
} else {
3044
setChannels((channels) => {
31-
const channelInList = channels.filter((channel) => channel.cid === event.cid).length > 0;
45+
const targetChannelIndex = channels.findIndex((channel) => channel.cid === event.cid);
46+
const targetChannelExistsWithinList = targetChannelIndex >= 0;
47+
48+
const isTargetChannelPinned = isChannelPinned({
49+
channel: channels[targetChannelIndex],
50+
userId: client.userID!,
51+
});
3252

33-
if (!channelInList && allowNewMessagesFromUnfilteredChannels && event.channel_type) {
34-
const channel = client.channel(event.channel_type, event.channel_id);
35-
return uniqBy([channel, ...channels], 'cid');
53+
if (
54+
// target channel is pinned
55+
(isTargetChannelPinned && considerPinnedChannels) ||
56+
// list order is locked
57+
lockChannelOrder ||
58+
// target channel is not within the loaded list and loading from cache is disallowed
59+
(!targetChannelExistsWithinList && !allowNewMessagesFromUnfilteredChannels)
60+
) {
61+
return channels;
3662
}
3763

38-
if (!lockChannelOrder) return moveChannelUp({ channels, cid: event.cid || '' });
64+
// we either have the channel to move or we pull it from the cache (or instantiate) if it's allowed
65+
const channelToMove: Channel<SCG> | null =
66+
channels[targetChannelIndex] ??
67+
(allowNewMessagesFromUnfilteredChannels && event.channel_type
68+
? client.channel(event.channel_type, event.channel_id)
69+
: null);
70+
71+
if (channelToMove) {
72+
return moveChannelUpwards({
73+
channels,
74+
channelToMove,
75+
channelToMoveIndexWithinChannels: targetChannelIndex,
76+
considerPinnedChannels,
77+
userId: client.userID!,
78+
});
79+
}
3980

4081
return channels;
4182
});
@@ -50,6 +91,7 @@ export const useMessageNewListener = <
5091
}, [
5192
allowNewMessagesFromUnfilteredChannels,
5293
client,
94+
considerPinnedChannels,
5395
customHandler,
5496
lockChannelOrder,
5597
setChannels,
Lines changed: 82 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,51 @@
11
import type { Channel } from 'stream-chat';
22
import uniqBy from 'lodash.uniqby';
33

4+
import { isChannelPinned } from './hooks';
5+
46
import type { DefaultStreamChatGenerics } from '../../types/types';
57

68
export const MAX_QUERY_CHANNELS_LIMIT = 30;
79

8-
type MoveChannelUpParams<
9-
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics
10-
> = {
11-
channels: Array<Channel<StreamChatGenerics>>;
10+
type MoveChannelUpParams<SCG extends DefaultStreamChatGenerics = DefaultStreamChatGenerics> = {
11+
channels: Array<Channel<SCG>>;
1212
cid: string;
13-
activeChannel?: Channel<StreamChatGenerics>;
13+
userId: string;
14+
activeChannel?: Channel<SCG>;
15+
channelIndexWithinChannels?: number;
16+
considerPinnedChannels?: boolean;
1417
};
1518

16-
export const moveChannelUp = <
17-
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics
18-
>({
19+
type MoveChannelUpwardsParams<SCG extends DefaultStreamChatGenerics = DefaultStreamChatGenerics> = {
20+
channels: Array<Channel<SCG>>;
21+
channelToMove: Channel<SCG>;
22+
/**
23+
* If the index of the channel within `channels` list which is being moved upwards
24+
* (`channelToMove`) is known, you can supply it to skip extra calculation.
25+
*/
26+
channelToMoveIndexWithinChannels?: number;
27+
/**
28+
* Pinned channels should not move within the list based on recent activity, channels which
29+
* receive messages and are not pinned should move upwards but only under the last pinned channel
30+
* in the list. Property defaults to `false` and should be calculated based on existence of
31+
* the `pinned_at` sort option.
32+
*/
33+
considerPinnedChannels?: boolean;
34+
/**
35+
* If `considerPinnedChannels` is set to `true`, then `userId` should be supplied - without it the
36+
* pinned channels won't be considered.
37+
*/
38+
userId?: string;
39+
};
40+
41+
/**
42+
* @deprecated
43+
*/
44+
export const moveChannelUp = <SCG extends DefaultStreamChatGenerics = DefaultStreamChatGenerics>({
1945
activeChannel,
2046
channels,
2147
cid,
22-
}: MoveChannelUpParams<StreamChatGenerics>) => {
48+
}: MoveChannelUpParams<SCG>) => {
2349
// get index of channel to move up
2450
const channelIndex = channels.findIndex((channel) => channel.cid === cid);
2551

@@ -30,3 +56,50 @@ export const moveChannelUp = <
3056

3157
return uniqBy([channel, ...channels], 'cid');
3258
};
59+
60+
export const moveChannelUpwards = <
61+
SCG extends DefaultStreamChatGenerics = DefaultStreamChatGenerics
62+
>({
63+
channels,
64+
channelToMove,
65+
channelToMoveIndexWithinChannels,
66+
considerPinnedChannels = false,
67+
userId,
68+
}: MoveChannelUpwardsParams<SCG>) => {
69+
// get index of channel to move up
70+
const targetChannelIndex =
71+
channelToMoveIndexWithinChannels ??
72+
channels.findIndex((channel) => channel.cid === channelToMove.cid);
73+
74+
const targetChannelExistsWithinList = targetChannelIndex >= 0;
75+
const targetChannelAlreadyAtTheTop = targetChannelIndex === 0;
76+
77+
if (targetChannelAlreadyAtTheTop) return channels;
78+
79+
// as position of pinned channels has to stay unchanged, we need to
80+
// find last pinned channel in the list to move the target channel after
81+
let lastPinIndex: number | null = null;
82+
if (considerPinnedChannels && userId) {
83+
for (const c of channels) {
84+
if (!isChannelPinned({ channel: c, userId })) break;
85+
86+
if (typeof lastPinIndex === 'number') {
87+
lastPinIndex++;
88+
} else {
89+
lastPinIndex = 0;
90+
}
91+
}
92+
}
93+
94+
const newChannels = [...channels];
95+
96+
// target channel index is known, remove it from the list
97+
if (targetChannelExistsWithinList) {
98+
newChannels.splice(targetChannelIndex, 1);
99+
}
100+
101+
// re-insert it at the new place (to specific index if pinned channels are considered)
102+
newChannels.splice(typeof lastPinIndex === 'number' ? lastPinIndex + 1 : 0, 0, channelToMove);
103+
104+
return newChannels;
105+
};

src/mock-builders/event/messageNew.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
export default (client, newMessage, channel = {}) => {
22
client.dispatchEvent({
33
channel,
4+
channel_id: channel.id,
5+
channel_type: channel.type,
46
cid: channel.cid,
57
message: newMessage,
68
type: 'message.new',

0 commit comments

Comments
 (0)