Skip to content

Commit

Permalink
feat: per-user channel pinning/archiving (#2555)
Browse files Browse the repository at this point in the history
### 🎯 Goal

Integrate channel pinning and archiving within SDK.

This PR relies on: GetStream/stream-chat-js#1409
and GetStream/stream-chat-css#321
  • Loading branch information
arnautov-anton authored Dec 12, 2024
1 parent 0577ffd commit a51fad0
Show file tree
Hide file tree
Showing 30 changed files with 1,075 additions and 159 deletions.
5 changes: 3 additions & 2 deletions examples/vite/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,10 @@ const userId = parseUserIdFromToken(userToken);
const filters: ChannelFilters = {
members: { $in: [userId] },
type: 'messaging',
archived: false,
};
const options: ChannelOptions = { limit: 3, presence: true, state: true };
const sort: ChannelSort = { last_message_at: -1, updated_at: -1 };
const options: ChannelOptions = { limit: 5, presence: true, state: true };
const sort: ChannelSort = [{ pinned_at: 1 }, { last_message_at: -1 }, { updated_at: -1 }];

type LocalAttachmentType = Record<string, unknown>;
type LocalChannelType = Record<string, unknown>;
Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@
"emoji-mart": "^5.4.0",
"react": "^18.0.0 || ^17.0.0 || ^16.8.0",
"react-dom": "^18.0.0 || ^17.0.0 || ^16.8.0",
"stream-chat": "^8.46.0"
"stream-chat": "^8.46.1"
},
"peerDependenciesMeta": {
"@breezystack/lamejs": {
Expand Down Expand Up @@ -187,7 +187,7 @@
"@semantic-release/exec": "^6.0.3",
"@semantic-release/git": "^10.0.1",
"@stream-io/rollup-plugin-node-builtins": "^2.1.5",
"@stream-io/stream-chat-css": "^5.5.0",
"@stream-io/stream-chat-css": "^5.6.0",
"@testing-library/jest-dom": "^6.1.4",
"@testing-library/react": "^13.1.1",
"@testing-library/react-hooks": "^8.0.0",
Expand Down Expand Up @@ -257,7 +257,7 @@
"react-dom": "^18.1.0",
"react-test-renderer": "^18.1.0",
"semantic-release": "^19.0.5",
"stream-chat": "^8.46.0",
"stream-chat": "^8.46.1",
"ts-jest": "^29.1.4",
"typescript": "^5.4.5"
},
Expand Down
94 changes: 40 additions & 54 deletions src/components/ChannelList/ChannelList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,11 @@ import React, { useCallback, useEffect, useRef, useState } from 'react';
import clsx from 'clsx';

import { ChannelListMessenger, ChannelListMessengerProps } from './ChannelListMessenger';
import { useChannelDeletedListener } from './hooks/useChannelDeletedListener';
import { useChannelHiddenListener } from './hooks/useChannelHiddenListener';
import { useChannelTruncatedListener } from './hooks/useChannelTruncatedListener';
import { useChannelUpdatedListener } from './hooks/useChannelUpdatedListener';
import { useChannelVisibleListener } from './hooks/useChannelVisibleListener';
import { useConnectionRecoveredListener } from './hooks/useConnectionRecoveredListener';
import { useMessageNewListener } from './hooks/useMessageNewListener';
import { useMobileNavigation } from './hooks/useMobileNavigation';
import { useNotificationAddedToChannelListener } from './hooks/useNotificationAddedToChannelListener';
import { useNotificationMessageNewListener } from './hooks/useNotificationMessageNewListener';
import { useNotificationRemovedFromChannelListener } from './hooks/useNotificationRemovedFromChannelListener';
import { CustomQueryChannelsFn, usePaginatedChannels } from './hooks/usePaginatedChannels';
import { useUserPresenceChangedListener } from './hooks/useUserPresenceChangedListener';
import { MAX_QUERY_CHANNELS_LIMIT, moveChannelUp } from './utils';
import { MAX_QUERY_CHANNELS_LIMIT, moveChannelUpwards } from './utils';

import { Avatar as DefaultAvatar } from '../Avatar';
import { ChannelPreview, ChannelPreviewUIComponentProps } from '../ChannelPreview/ChannelPreview';
import {
Expand All @@ -37,6 +28,7 @@ import type { Channel, ChannelFilters, ChannelOptions, ChannelSort, Event } from
import type { ChannelAvatarProps } from '../Avatar';
import type { TranslationContextValue } from '../../context/TranslationContext';
import type { DefaultStreamChatGenerics, PaginatorProps } from '../../types/types';
import { useChannelListShape, usePrepareShapeHandlers } from './hooks/useChannelListShape';

const DEFAULT_FILTERS = {};
const DEFAULT_OPTIONS = {};
Expand All @@ -62,6 +54,7 @@ export type ChannelListProps<
) => Array<Channel<StreamChatGenerics>>;
/** 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) */
ChannelSearch?: React.ComponentType<ChannelSearchProps<StreamChatGenerics>>;
// FIXME: how is this even legal (WHY IS IT STRING?!)
/** Set a channel (with this ID) to active and manually move it to the top of the list */
customActiveChannel?: string;
/** 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. */
Expand Down Expand Up @@ -160,26 +153,24 @@ export type ChannelListProps<
watchers?: { limit?: number; offset?: number };
};

const UnMemoizedChannelList = <
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics
>(
props: ChannelListProps<StreamChatGenerics>,
const UnMemoizedChannelList = <SCG extends DefaultStreamChatGenerics = DefaultStreamChatGenerics>(
props: ChannelListProps<SCG>,
) => {
const {
additionalChannelSearchProps,
Avatar = DefaultAvatar,
allowNewMessagesFromUnfilteredChannels,
allowNewMessagesFromUnfilteredChannels = true,
channelRenderFilterFn,
ChannelSearch = DefaultChannelSearch,
customActiveChannel,
customQueryChannels,
EmptyStateIndicator = DefaultEmptyStateIndicator,
filters,
filters = {},
getLatestMessagePreview,
LoadingErrorIndicator = NullComponent,
LoadingIndicator = LoadingChannels,
List = ChannelListMessenger,
lockChannelOrder,
lockChannelOrder = false,
onAddedToChannel,
onChannelDeleted,
onChannelHidden,
Expand Down Expand Up @@ -211,7 +202,7 @@ const UnMemoizedChannelList = <
setActiveChannel,
theme,
useImageFlagEmojisOnWindows,
} = useChatContext<StreamChatGenerics>('ChannelList');
} = useChatContext<SCG>('ChannelList');

const channelListRef = useRef<HTMLDivElement>(null);
const [channelUpdateCount, setChannelUpdateCount] = useState(0);
Expand All @@ -221,14 +212,15 @@ const UnMemoizedChannelList = <
* If customActiveChannel prop is absent, then set the first channel in list as active channel.
*/
const activeChannelHandler = async (
channels: Array<Channel<StreamChatGenerics>>,
setChannels: React.Dispatch<React.SetStateAction<Array<Channel<StreamChatGenerics>>>>,
channels: Array<Channel<SCG>>,
setChannels: React.Dispatch<React.SetStateAction<Array<Channel<SCG>>>>,
) => {
if (!channels.length || channels.length > (options?.limit || MAX_QUERY_CHANNELS_LIMIT)) {
return;
}

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

if (!customActiveChannelObject) {
Expand All @@ -239,10 +231,10 @@ const UnMemoizedChannelList = <
if (customActiveChannelObject) {
setActiveChannel(customActiveChannelObject, watchers);

const newChannels = moveChannelUp({
activeChannel: customActiveChannelObject,
const newChannels = moveChannelUpwards({
channels,
cid: customActiveChannelObject.cid,
channelToMove: customActiveChannelObject,
sort,
});

setChannels(newChannels);
Expand All @@ -260,16 +252,11 @@ const UnMemoizedChannelList = <
* For some events, inner properties on the channel will update but the shallow comparison will not
* force a re-render. Incrementing this dummy variable ensures the channel previews update.
*/
const forceUpdate = useCallback(() => setChannelUpdateCount((count) => count + 1), [
setChannelUpdateCount,
]);
const forceUpdate = useCallback(() => setChannelUpdateCount((count) => count + 1), []);

const onSearch = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
if (!event.target.value) {
setSearchActive(false);
} else {
setSearchActive(true);
}
setSearchActive(!!event.target.value);

additionalChannelSearchProps?.onSearch?.(event);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
Expand All @@ -294,33 +281,32 @@ const UnMemoizedChannelList = <

useMobileNavigation(channelListRef, navOpen, closeMobileNav);

useMessageNewListener(
setChannels,
onMessageNewHandler,
lockChannelOrder,
const { customHandler, defaultHandler } = usePrepareShapeHandlers<SCG>({
allowNewMessagesFromUnfilteredChannels,
);
useNotificationMessageNewListener(
setChannels,
filters,
lockChannelOrder,
onAddedToChannel,
onChannelDeleted,
onChannelHidden,
onChannelTruncated,
onChannelUpdated,
onChannelVisible,
onMessageNew,
allowNewMessagesFromUnfilteredChannels,
);
useNotificationAddedToChannelListener(
onMessageNewHandler,
onRemovedFromChannel,
setChannels,
onAddedToChannel,
allowNewMessagesFromUnfilteredChannels,
);
useNotificationRemovedFromChannelListener(setChannels, onRemovedFromChannel);
useChannelDeletedListener(setChannels, onChannelDeleted);
useChannelHiddenListener(setChannels, onChannelHidden);
useChannelVisibleListener(setChannels, onChannelVisible);
useChannelTruncatedListener(setChannels, onChannelTruncated, forceUpdate);
useChannelUpdatedListener(setChannels, onChannelUpdated, forceUpdate);
sort,
// TODO: implement
// customHandleChannelListShape
});

useChannelListShape<SCG>(customHandler ?? defaultHandler);

// TODO: maybe move this too
useConnectionRecoveredListener(forceUpdate);
useUserPresenceChangedListener(setChannels);

useEffect(() => {
const handleEvent = (event: Event<StreamChatGenerics>) => {
const handleEvent = (event: Event<SCG>) => {
if (event.cid === channel?.cid) {
setActiveChannel();
}
Expand All @@ -336,7 +322,7 @@ const UnMemoizedChannelList = <
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [channel?.cid]);

const renderChannel = (item: Channel<StreamChatGenerics>) => {
const renderChannel = (item: Channel<SCG>) => {
const previewProps = {
activeChannel: channel,
Avatar,
Expand Down
2 changes: 1 addition & 1 deletion src/components/ChannelList/__tests__/ChannelList.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import {
queryChannelsApi,
queryUsersApi,
useMockedApis,
} from 'mock-builders';
} from '../../../mock-builders';

import { Chat } from '../../Chat';
import { ChannelList } from '../ChannelList';
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';
Loading

0 comments on commit a51fad0

Please sign in to comment.