Skip to content

Commit

Permalink
Move things around, use ChannelState.membership instead
Browse files Browse the repository at this point in the history
  • Loading branch information
arnautov-anton committed Nov 19, 2024
1 parent bf2999c commit d7a0d12
Show file tree
Hide file tree
Showing 7 changed files with 167 additions and 53 deletions.
18 changes: 14 additions & 4 deletions src/components/ChannelList/ChannelList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,12 @@ import { useNotificationMessageNewListener } from './hooks/useNotificationMessag
import { useNotificationRemovedFromChannelListener } from './hooks/useNotificationRemovedFromChannelListener';
import { CustomQueryChannelsFn, usePaginatedChannels } from './hooks/usePaginatedChannels';
import { useUserPresenceChangedListener } from './hooks/useUserPresenceChangedListener';
import { MAX_QUERY_CHANNELS_LIMIT, moveChannelUpwards } from './utils';
import { useMemberUpdatedListener } from './hooks/useMemberUpdatedListener';
import {
MAX_QUERY_CHANNELS_LIMIT,
moveChannelUpwards,
shouldConsiderPinnedChannels,
} from './utils';

import { AvatarProps, Avatar as DefaultAvatar } from '../Avatar/Avatar';
import { ChannelPreview, ChannelPreviewUIComponentProps } from '../ChannelPreview/ChannelPreview';
Expand Down Expand Up @@ -244,8 +249,7 @@ const UnMemoizedChannelList = <
channels,
channelToMove: customActiveChannelObject,
// TODO: adjust acordingly (based on sort)
considerPinnedChannels: false,
userId: client.userID!,
considerPinnedChannels: shouldConsiderPinnedChannels(sort),
});

setChannels(newChannels);
Expand Down Expand Up @@ -295,6 +299,8 @@ const UnMemoizedChannelList = <

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

const considerPinnedChannels = shouldConsiderPinnedChannels(sort);

useMobileNavigation(channelListRef, navOpen, closeMobileNav);

useMessageNewListener(
Expand All @@ -303,7 +309,7 @@ const UnMemoizedChannelList = <
lockChannelOrder,
allowNewMessagesFromUnfilteredChannels,
// TODO: adjust accordingly (consider sort option)
false,
considerPinnedChannels,
);
useNotificationMessageNewListener(
setChannels,
Expand All @@ -315,6 +321,10 @@ const UnMemoizedChannelList = <
onAddedToChannel,
allowNewMessagesFromUnfilteredChannels,
);
useMemberUpdatedListener({
considerPinnedChannels,
setChannels,
});
useNotificationRemovedFromChannelListener(setChannels, onRemovedFromChannel);
useChannelDeletedListener(setChannels, onChannelDeleted);
useChannelHiddenListener(setChannels, onChannelHidden);
Expand Down
1 change: 1 addition & 0 deletions src/components/ChannelList/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ export * from './useNotificationMessageNewListener';
export * from './useNotificationRemovedFromChannelListener';
export * from './usePaginatedChannels';
export * from './useUserPresenceChangedListener';
export * from './useChannelMembershipState';
28 changes: 28 additions & 0 deletions src/components/ChannelList/hooks/useChannelMembershipState.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { useEffect, useState } from 'react';
import type { Channel, ChannelState, ExtendableGenerics } from 'stream-chat';

import { useChatContext } from '../../../context';

export const useChannelMembershipState = <SCG extends ExtendableGenerics>(
channel?: Channel<SCG>,
) => {
const [membership, setMembership] = useState<ChannelState<SCG>['membership']>(

Check warning on line 9 in src/components/ChannelList/hooks/useChannelMembershipState.ts

View check run for this annotation

Codecov / codecov/patch

src/components/ChannelList/hooks/useChannelMembershipState.ts#L9

Added line #L9 was not covered by tests
channel?.state.membership || {},
);

const { client } = useChatContext<SCG>();

Check warning on line 13 in src/components/ChannelList/hooks/useChannelMembershipState.ts

View check run for this annotation

Codecov / codecov/patch

src/components/ChannelList/hooks/useChannelMembershipState.ts#L13

Added line #L13 was not covered by tests

useEffect(() => {

Check warning on line 15 in src/components/ChannelList/hooks/useChannelMembershipState.ts

View check run for this annotation

Codecov / codecov/patch

src/components/ChannelList/hooks/useChannelMembershipState.ts#L15

Added line #L15 was not covered by tests
if (!channel) return;

const subscriptions = ['member.updated'].map((v) =>
client.on(v, () => {
setMembership(channel.state.membership);

Check warning on line 20 in src/components/ChannelList/hooks/useChannelMembershipState.ts

View check run for this annotation

Codecov / codecov/patch

src/components/ChannelList/hooks/useChannelMembershipState.ts#L18-L20

Added lines #L18 - L20 were not covered by tests
}),
);

return () => subscriptions.forEach((subscription) => subscription.unsubscribe());

Check warning on line 24 in src/components/ChannelList/hooks/useChannelMembershipState.ts

View check run for this annotation

Codecov / codecov/patch

src/components/ChannelList/hooks/useChannelMembershipState.ts#L24

Added line #L24 was not covered by tests
}, [client, channel]);

return membership;

Check warning on line 27 in src/components/ChannelList/hooks/useChannelMembershipState.ts

View check run for this annotation

Codecov / codecov/patch

src/components/ChannelList/hooks/useChannelMembershipState.ts#L27

Added line #L27 was not covered by tests
};
57 changes: 57 additions & 0 deletions src/components/ChannelList/hooks/useMemberUpdatedListener.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { useEffect } from 'react';
import type { Dispatch, SetStateAction } from 'react';
import type { Channel, ExtendableGenerics } from 'stream-chat';

import { useChatContext } from '../../../context';
import { findLastPinnedChannelIndex } from '../utils';

export const useMemberUpdatedListener = <SCG extends ExtendableGenerics>({
considerPinnedChannels = false,
lockChannelOrder = false,
setChannels,
}: {
setChannels: Dispatch<SetStateAction<Channel<SCG>[]>>;
considerPinnedChannels?: boolean;
lockChannelOrder?: boolean;
}) => {
const { client } = useChatContext<SCG>();

useEffect(() => {
// do nothing if channel order is locked or pinned channels aren't being considered
if (lockChannelOrder || !considerPinnedChannels) return;

const subscription = client.on('member.updated', (e) => {

Check warning on line 23 in src/components/ChannelList/hooks/useMemberUpdatedListener.ts

View check run for this annotation

Codecov / codecov/patch

src/components/ChannelList/hooks/useMemberUpdatedListener.ts#L23

Added line #L23 was not covered by tests
if (!e.member || !e.channel_type) return;
// const member = e.member;
const channelType = e.channel_type;
const channelId = e.channel_id;

Check warning on line 27 in src/components/ChannelList/hooks/useMemberUpdatedListener.ts

View check run for this annotation

Codecov / codecov/patch

src/components/ChannelList/hooks/useMemberUpdatedListener.ts#L26-L27

Added lines #L26 - L27 were not covered by tests

setChannels((currentChannels) => {
const targetChannel = client.channel(channelType, channelId);

Check warning on line 30 in src/components/ChannelList/hooks/useMemberUpdatedListener.ts

View check run for this annotation

Codecov / codecov/patch

src/components/ChannelList/hooks/useMemberUpdatedListener.ts#L29-L30

Added lines #L29 - L30 were not covered by tests
// assumes that channel instances are not changing
const targetChannelIndex = currentChannels.indexOf(targetChannel);
const targetChannelExistsWithinList = targetChannelIndex >= 0;

Check warning on line 33 in src/components/ChannelList/hooks/useMemberUpdatedListener.ts

View check run for this annotation

Codecov / codecov/patch

src/components/ChannelList/hooks/useMemberUpdatedListener.ts#L32-L33

Added lines #L32 - L33 were not covered by tests

const newChannels = [...currentChannels];

Check warning on line 35 in src/components/ChannelList/hooks/useMemberUpdatedListener.ts

View check run for this annotation

Codecov / codecov/patch

src/components/ChannelList/hooks/useMemberUpdatedListener.ts#L35

Added line #L35 was not covered by tests

if (targetChannelExistsWithinList) {
newChannels.splice(targetChannelIndex, 1);

Check warning on line 38 in src/components/ChannelList/hooks/useMemberUpdatedListener.ts

View check run for this annotation

Codecov / codecov/patch

src/components/ChannelList/hooks/useMemberUpdatedListener.ts#L38

Added line #L38 was not covered by tests
}

const lastPinnedChannelIndex = findLastPinnedChannelIndex({ channels: newChannels }) ?? 0;
const newTargetChannelIndex = lastPinnedChannelIndex + 1;

Check warning on line 42 in src/components/ChannelList/hooks/useMemberUpdatedListener.ts

View check run for this annotation

Codecov / codecov/patch

src/components/ChannelList/hooks/useMemberUpdatedListener.ts#L42

Added line #L42 was not covered by tests

// skip re-render if the position of the channel does not change
if (currentChannels[newTargetChannelIndex] === targetChannel) {
return currentChannels;

Check warning on line 46 in src/components/ChannelList/hooks/useMemberUpdatedListener.ts

View check run for this annotation

Codecov / codecov/patch

src/components/ChannelList/hooks/useMemberUpdatedListener.ts#L46

Added line #L46 was not covered by tests
}

newChannels.splice(newTargetChannelIndex, 0, targetChannel);

Check warning on line 49 in src/components/ChannelList/hooks/useMemberUpdatedListener.ts

View check run for this annotation

Codecov / codecov/patch

src/components/ChannelList/hooks/useMemberUpdatedListener.ts#L49

Added line #L49 was not covered by tests

return newChannels;

Check warning on line 51 in src/components/ChannelList/hooks/useMemberUpdatedListener.ts

View check run for this annotation

Codecov / codecov/patch

src/components/ChannelList/hooks/useMemberUpdatedListener.ts#L51

Added line #L51 was not covered by tests
});
});

return subscription.unsubscribe;

Check warning on line 55 in src/components/ChannelList/hooks/useMemberUpdatedListener.ts

View check run for this annotation

Codecov / codecov/patch

src/components/ChannelList/hooks/useMemberUpdatedListener.ts#L55

Added line #L55 was not covered by tests
}, [client, considerPinnedChannels, lockChannelOrder, setChannels]);
};
12 changes: 3 additions & 9 deletions src/components/ChannelList/hooks/useMessageNewListener.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,19 @@
import { useEffect } from 'react';
import type { Dispatch, SetStateAction } from 'react';

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

import type { Channel, Event, ExtendableGenerics } from 'stream-chat';

import type { DefaultStreamChatGenerics } from '../../../types/types';
import { moveChannelUpwards } from '../utils';
import { useChatContext } from '../../../context/ChatContext';
import type { DefaultStreamChatGenerics } from '../../../types/types';

export const isChannelPinned = <SCG extends ExtendableGenerics>({
channel,
userId,
}: {
userId: string;
channel?: Channel<SCG>;
}) => {
if (!channel) return false;

const member = channel.state.members[userId];
const member = channel.state.membership;

return !!member?.pinned_at;
};
Expand Down Expand Up @@ -47,7 +43,6 @@ export const useMessageNewListener = <

const isTargetChannelPinned = isChannelPinned({
channel: channels[targetChannelIndex],
userId: client.userID!,
});

if (
Expand All @@ -74,7 +69,6 @@ export const useMessageNewListener = <
channelToMove,
channelToMoveIndexWithinChannels: targetChannelIndex,
considerPinnedChannels,
userId: client.userID!,
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type { Channel, Event } from 'stream-chat';

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

// TODO: re-visit this and adjust (apply pinned channels)
export const useNotificationMessageNewListener = <
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics
>(
Expand Down
103 changes: 63 additions & 40 deletions src/components/ChannelList/utils.ts
Original file line number Diff line number Diff line change
@@ -1,41 +1,16 @@
import type { Channel } from 'stream-chat';
import uniqBy from 'lodash.uniqby';
import type { Channel, ExtendableGenerics } from 'stream-chat';

import { isChannelPinned } from './hooks';

import type { DefaultStreamChatGenerics } from '../../types/types';
import { ChannelListProps } from './ChannelList';

export const MAX_QUERY_CHANNELS_LIMIT = 30;

type MoveChannelUpParams<SCG extends DefaultStreamChatGenerics = DefaultStreamChatGenerics> = {
channels: Array<Channel<SCG>>;
cid: string;
userId: string;
activeChannel?: Channel<SCG>;
channelIndexWithinChannels?: number;
considerPinnedChannels?: boolean;
};

type MoveChannelUpwardsParams<SCG extends DefaultStreamChatGenerics = DefaultStreamChatGenerics> = {
channels: Array<Channel<SCG>>;
channelToMove: Channel<SCG>;
/**
* If the index of the channel within `channels` list which is being moved upwards
* (`channelToMove`) is known, you can supply it to skip extra calculation.
*/
channelToMoveIndexWithinChannels?: number;
/**
* Pinned channels should not move within the list based on recent activity, channels which
* receive messages and are not pinned should move upwards but only under the last pinned channel
* in the list. Property defaults to `false` and should be calculated based on existence of
* the `pinned_at` sort option.
*/
considerPinnedChannels?: boolean;
/**
* If `considerPinnedChannels` is set to `true`, then `userId` should be supplied - without it the
* pinned channels won't be considered.
*/
userId?: string;
};

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

/**
* Expects channel array sorted by `{ pinned_at: -1 }`.
*
* TODO: add support for the `{ pinned_at: 1 }`
*/
export function findLastPinnedChannelIndex<SCG extends ExtendableGenerics>({
channels,
}: {
channels: Channel<SCG>[];
}) {
let lastPinnedChannelIndex: number | null = null;

Check warning on line 45 in src/components/ChannelList/utils.ts

View check run for this annotation

Codecov / codecov/patch

src/components/ChannelList/utils.ts#L45

Added line #L45 was not covered by tests

for (const channel of channels) {

Check warning on line 47 in src/components/ChannelList/utils.ts

View check run for this annotation

Codecov / codecov/patch

src/components/ChannelList/utils.ts#L47

Added line #L47 was not covered by tests
if (!isChannelPinned({ channel })) break;

if (typeof lastPinnedChannelIndex === 'number') {
lastPinnedChannelIndex++;

Check warning on line 51 in src/components/ChannelList/utils.ts

View check run for this annotation

Codecov / codecov/patch

src/components/ChannelList/utils.ts#L51

Added line #L51 was not covered by tests
} else {
lastPinnedChannelIndex = 0;

Check warning on line 53 in src/components/ChannelList/utils.ts

View check run for this annotation

Codecov / codecov/patch

src/components/ChannelList/utils.ts#L53

Added line #L53 was not covered by tests
}
}

return lastPinnedChannelIndex;

Check warning on line 57 in src/components/ChannelList/utils.ts

View check run for this annotation

Codecov / codecov/patch

src/components/ChannelList/utils.ts#L57

Added line #L57 was not covered by tests
}

type MoveChannelUpwardsParams<SCG extends DefaultStreamChatGenerics = DefaultStreamChatGenerics> = {
channels: Array<Channel<SCG>>;
channelToMove: Channel<SCG>;
/**
* If the index of the channel within `channels` list which is being moved upwards
* (`channelToMove`) is known, you can supply it to skip extra calculation.
*/
channelToMoveIndexWithinChannels?: number;
/**
* Pinned channels should not move within the list based on recent activity, channels which
* receive messages and are not pinned should move upwards but only under the last pinned channel
* in the list. Property defaults to `false` and should be calculated based on existence of
* the `pinned_at` sort option.
*/
considerPinnedChannels?: boolean;
};

export const moveChannelUpwards = <
SCG extends DefaultStreamChatGenerics = DefaultStreamChatGenerics
>({
channels,
channelToMove,
channelToMoveIndexWithinChannels,
considerPinnedChannels = false,
userId,
}: MoveChannelUpwardsParams<SCG>) => {
// get index of channel to move up
const targetChannelIndex =
Expand All @@ -78,17 +94,9 @@ export const moveChannelUpwards = <

// as position of pinned channels has to stay unchanged, we need to
// find last pinned channel in the list to move the target channel after
let lastPinIndex: number | null = null;
if (considerPinnedChannels && userId) {
for (const c of channels) {
if (!isChannelPinned({ channel: c, userId })) break;

if (typeof lastPinIndex === 'number') {
lastPinIndex++;
} else {
lastPinIndex = 0;
}
}
let lastPinnedChannelIndex: number | null = null;
if (considerPinnedChannels) {
lastPinnedChannelIndex = findLastPinnedChannelIndex({ channels });

Check warning on line 99 in src/components/ChannelList/utils.ts

View check run for this annotation

Codecov / codecov/patch

src/components/ChannelList/utils.ts#L99

Added line #L99 was not covered by tests
}

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

// re-insert it at the new place (to specific index if pinned channels are considered)
newChannels.splice(typeof lastPinIndex === 'number' ? lastPinIndex + 1 : 0, 0, channelToMove);
newChannels.splice(
typeof lastPinnedChannelIndex === 'number' ? lastPinnedChannelIndex + 1 : 0,
0,
channelToMove,
);

return newChannels;
};

// TODO: adjust and re-test when the actual behavior is implemented by the BE
export const shouldConsiderPinnedChannels = (sort: ChannelListProps['sort']) => {
if (!sort) return false;

if (Array.isArray(sort)) {
return sort.some((v) => v.pinned_at === -1);

Check warning on line 124 in src/components/ChannelList/utils.ts

View check run for this annotation

Codecov / codecov/patch

src/components/ChannelList/utils.ts#L124

Added line #L124 was not covered by tests
}

return sort.pinned_at === -1;
};

0 comments on commit d7a0d12

Please sign in to comment.