Skip to content

Commit d7a0d12

Browse files
Move things around, use ChannelState.membership instead
1 parent bf2999c commit d7a0d12

File tree

7 files changed

+167
-53
lines changed

7 files changed

+167
-53
lines changed

src/components/ChannelList/ChannelList.tsx

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,12 @@ 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, moveChannelUpwards } from './utils';
18+
import { useMemberUpdatedListener } from './hooks/useMemberUpdatedListener';
19+
import {
20+
MAX_QUERY_CHANNELS_LIMIT,
21+
moveChannelUpwards,
22+
shouldConsiderPinnedChannels,
23+
} from './utils';
1924

2025
import { AvatarProps, Avatar as DefaultAvatar } from '../Avatar/Avatar';
2126
import { ChannelPreview, ChannelPreviewUIComponentProps } from '../ChannelPreview/ChannelPreview';
@@ -244,8 +249,7 @@ const UnMemoizedChannelList = <
244249
channels,
245250
channelToMove: customActiveChannelObject,
246251
// TODO: adjust acordingly (based on sort)
247-
considerPinnedChannels: false,
248-
userId: client.userID!,
252+
considerPinnedChannels: shouldConsiderPinnedChannels(sort),
249253
});
250254

251255
setChannels(newChannels);
@@ -295,6 +299,8 @@ const UnMemoizedChannelList = <
295299

296300
const loadedChannels = channelRenderFilterFn ? channelRenderFilterFn(channels) : channels;
297301

302+
const considerPinnedChannels = shouldConsiderPinnedChannels(sort);
303+
298304
useMobileNavigation(channelListRef, navOpen, closeMobileNav);
299305

300306
useMessageNewListener(
@@ -303,7 +309,7 @@ const UnMemoizedChannelList = <
303309
lockChannelOrder,
304310
allowNewMessagesFromUnfilteredChannels,
305311
// TODO: adjust accordingly (consider sort option)
306-
false,
312+
considerPinnedChannels,
307313
);
308314
useNotificationMessageNewListener(
309315
setChannels,
@@ -315,6 +321,10 @@ const UnMemoizedChannelList = <
315321
onAddedToChannel,
316322
allowNewMessagesFromUnfilteredChannels,
317323
);
324+
useMemberUpdatedListener({
325+
considerPinnedChannels,
326+
setChannels,
327+
});
318328
useNotificationRemovedFromChannelListener(setChannels, onRemovedFromChannel);
319329
useChannelDeletedListener(setChannels, onChannelDeleted);
320330
useChannelHiddenListener(setChannels, onChannelHidden);

src/components/ChannelList/hooks/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@ export * from './useNotificationMessageNewListener';
1111
export * from './useNotificationRemovedFromChannelListener';
1212
export * from './usePaginatedChannels';
1313
export * from './useUserPresenceChangedListener';
14+
export * from './useChannelMembershipState';
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { useEffect, useState } from 'react';
2+
import type { Channel, ChannelState, ExtendableGenerics } from 'stream-chat';
3+
4+
import { useChatContext } from '../../../context';
5+
6+
export const useChannelMembershipState = <SCG extends ExtendableGenerics>(
7+
channel?: Channel<SCG>,
8+
) => {
9+
const [membership, setMembership] = useState<ChannelState<SCG>['membership']>(
10+
channel?.state.membership || {},
11+
);
12+
13+
const { client } = useChatContext<SCG>();
14+
15+
useEffect(() => {
16+
if (!channel) return;
17+
18+
const subscriptions = ['member.updated'].map((v) =>
19+
client.on(v, () => {
20+
setMembership(channel.state.membership);
21+
}),
22+
);
23+
24+
return () => subscriptions.forEach((subscription) => subscription.unsubscribe());
25+
}, [client, channel]);
26+
27+
return membership;
28+
};
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { useEffect } from 'react';
2+
import type { Dispatch, SetStateAction } from 'react';
3+
import type { Channel, ExtendableGenerics } from 'stream-chat';
4+
5+
import { useChatContext } from '../../../context';
6+
import { findLastPinnedChannelIndex } from '../utils';
7+
8+
export const useMemberUpdatedListener = <SCG extends ExtendableGenerics>({
9+
considerPinnedChannels = false,
10+
lockChannelOrder = false,
11+
setChannels,
12+
}: {
13+
setChannels: Dispatch<SetStateAction<Channel<SCG>[]>>;
14+
considerPinnedChannels?: boolean;
15+
lockChannelOrder?: boolean;
16+
}) => {
17+
const { client } = useChatContext<SCG>();
18+
19+
useEffect(() => {
20+
// do nothing if channel order is locked or pinned channels aren't being considered
21+
if (lockChannelOrder || !considerPinnedChannels) return;
22+
23+
const subscription = client.on('member.updated', (e) => {
24+
if (!e.member || !e.channel_type) return;
25+
// const member = e.member;
26+
const channelType = e.channel_type;
27+
const channelId = e.channel_id;
28+
29+
setChannels((currentChannels) => {
30+
const targetChannel = client.channel(channelType, channelId);
31+
// assumes that channel instances are not changing
32+
const targetChannelIndex = currentChannels.indexOf(targetChannel);
33+
const targetChannelExistsWithinList = targetChannelIndex >= 0;
34+
35+
const newChannels = [...currentChannels];
36+
37+
if (targetChannelExistsWithinList) {
38+
newChannels.splice(targetChannelIndex, 1);
39+
}
40+
41+
const lastPinnedChannelIndex = findLastPinnedChannelIndex({ channels: newChannels }) ?? 0;
42+
const newTargetChannelIndex = lastPinnedChannelIndex + 1;
43+
44+
// skip re-render if the position of the channel does not change
45+
if (currentChannels[newTargetChannelIndex] === targetChannel) {
46+
return currentChannels;
47+
}
48+
49+
newChannels.splice(newTargetChannelIndex, 0, targetChannel);
50+
51+
return newChannels;
52+
});
53+
});
54+
55+
return subscription.unsubscribe;
56+
}, [client, considerPinnedChannels, lockChannelOrder, setChannels]);
57+
};

src/components/ChannelList/hooks/useMessageNewListener.ts

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,19 @@
11
import { useEffect } from 'react';
22
import type { Dispatch, SetStateAction } from 'react';
3-
4-
import { useChatContext } from '../../../context/ChatContext';
5-
63
import type { Channel, Event, ExtendableGenerics } from 'stream-chat';
74

8-
import type { DefaultStreamChatGenerics } from '../../../types/types';
95
import { moveChannelUpwards } from '../utils';
6+
import { useChatContext } from '../../../context/ChatContext';
7+
import type { DefaultStreamChatGenerics } from '../../../types/types';
108

119
export const isChannelPinned = <SCG extends ExtendableGenerics>({
1210
channel,
13-
userId,
1411
}: {
15-
userId: string;
1612
channel?: Channel<SCG>;
1713
}) => {
1814
if (!channel) return false;
1915

20-
const member = channel.state.members[userId];
16+
const member = channel.state.membership;
2117

2218
return !!member?.pinned_at;
2319
};
@@ -47,7 +43,6 @@ export const useMessageNewListener = <
4743

4844
const isTargetChannelPinned = isChannelPinned({
4945
channel: channels[targetChannelIndex],
50-
userId: client.userID!,
5146
});
5247

5348
if (
@@ -74,7 +69,6 @@ export const useMessageNewListener = <
7469
channelToMove,
7570
channelToMoveIndexWithinChannels: targetChannelIndex,
7671
considerPinnedChannels,
77-
userId: client.userID!,
7872
});
7973
}
8074

src/components/ChannelList/hooks/useNotificationMessageNewListener.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import type { Channel, Event } from 'stream-chat';
99

1010
import type { DefaultStreamChatGenerics } from '../../../types/types';
1111

12+
// TODO: re-visit this and adjust (apply pinned channels)
1213
export const useNotificationMessageNewListener = <
1314
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics
1415
>(
Lines changed: 63 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,16 @@
1-
import type { Channel } from 'stream-chat';
21
import uniqBy from 'lodash.uniqby';
2+
import type { Channel, ExtendableGenerics } from 'stream-chat';
33

44
import { isChannelPinned } from './hooks';
5-
65
import type { DefaultStreamChatGenerics } from '../../types/types';
6+
import { ChannelListProps } from './ChannelList';
77

88
export const MAX_QUERY_CHANNELS_LIMIT = 30;
99

1010
type MoveChannelUpParams<SCG extends DefaultStreamChatGenerics = DefaultStreamChatGenerics> = {
1111
channels: Array<Channel<SCG>>;
1212
cid: string;
13-
userId: string;
1413
activeChannel?: Channel<SCG>;
15-
channelIndexWithinChannels?: number;
16-
considerPinnedChannels?: boolean;
17-
};
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;
3914
};
4015

4116
/**
@@ -57,14 +32,55 @@ export const moveChannelUp = <SCG extends DefaultStreamChatGenerics = DefaultStr
5732
return uniqBy([channel, ...channels], 'cid');
5833
};
5934

35+
/**
36+
* Expects channel array sorted by `{ pinned_at: -1 }`.
37+
*
38+
* TODO: add support for the `{ pinned_at: 1 }`
39+
*/
40+
export function findLastPinnedChannelIndex<SCG extends ExtendableGenerics>({
41+
channels,
42+
}: {
43+
channels: Channel<SCG>[];
44+
}) {
45+
let lastPinnedChannelIndex: number | null = null;
46+
47+
for (const channel of channels) {
48+
if (!isChannelPinned({ channel })) break;
49+
50+
if (typeof lastPinnedChannelIndex === 'number') {
51+
lastPinnedChannelIndex++;
52+
} else {
53+
lastPinnedChannelIndex = 0;
54+
}
55+
}
56+
57+
return lastPinnedChannelIndex;
58+
}
59+
60+
type MoveChannelUpwardsParams<SCG extends DefaultStreamChatGenerics = DefaultStreamChatGenerics> = {
61+
channels: Array<Channel<SCG>>;
62+
channelToMove: Channel<SCG>;
63+
/**
64+
* If the index of the channel within `channels` list which is being moved upwards
65+
* (`channelToMove`) is known, you can supply it to skip extra calculation.
66+
*/
67+
channelToMoveIndexWithinChannels?: number;
68+
/**
69+
* Pinned channels should not move within the list based on recent activity, channels which
70+
* receive messages and are not pinned should move upwards but only under the last pinned channel
71+
* in the list. Property defaults to `false` and should be calculated based on existence of
72+
* the `pinned_at` sort option.
73+
*/
74+
considerPinnedChannels?: boolean;
75+
};
76+
6077
export const moveChannelUpwards = <
6178
SCG extends DefaultStreamChatGenerics = DefaultStreamChatGenerics
6279
>({
6380
channels,
6481
channelToMove,
6582
channelToMoveIndexWithinChannels,
6683
considerPinnedChannels = false,
67-
userId,
6884
}: MoveChannelUpwardsParams<SCG>) => {
6985
// get index of channel to move up
7086
const targetChannelIndex =
@@ -78,17 +94,9 @@ export const moveChannelUpwards = <
7894

7995
// as position of pinned channels has to stay unchanged, we need to
8096
// 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-
}
97+
let lastPinnedChannelIndex: number | null = null;
98+
if (considerPinnedChannels) {
99+
lastPinnedChannelIndex = findLastPinnedChannelIndex({ channels });
92100
}
93101

94102
const newChannels = [...channels];
@@ -99,7 +107,22 @@ export const moveChannelUpwards = <
99107
}
100108

101109
// 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);
110+
newChannels.splice(
111+
typeof lastPinnedChannelIndex === 'number' ? lastPinnedChannelIndex + 1 : 0,
112+
0,
113+
channelToMove,
114+
);
103115

104116
return newChannels;
105117
};
118+
119+
// TODO: adjust and re-test when the actual behavior is implemented by the BE
120+
export const shouldConsiderPinnedChannels = (sort: ChannelListProps['sort']) => {
121+
if (!sort) return false;
122+
123+
if (Array.isArray(sort)) {
124+
return sort.some((v) => v.pinned_at === -1);
125+
}
126+
127+
return sort.pinned_at === -1;
128+
};

0 commit comments

Comments
 (0)