diff --git a/examples/vite/src/App.tsx b/examples/vite/src/App.tsx index ea1c7170e..5949630cf 100644 --- a/examples/vite/src/App.tsx +++ b/examples/vite/src/App.tsx @@ -20,6 +20,7 @@ import { createCommandInjectionMiddleware, createCommandStringExtractionMiddleware, createDraftCommandInjectionMiddleware, + createPriorityOwnershipResolver, SearchController, UserSearchSource, } from 'stream-chat'; @@ -123,6 +124,10 @@ if (!apiKey) { // page size is a paginator concern and is passed via `paginatorOptions.pageSize` instead. const CHANNELS_PAGE_SIZE = 10; +// Unlike the archived / muted / default lists, which are mutually exclusive buckets, the unread +// inbox is a view over them: a channel with unread messages belongs both here and in "My channels". +const UNREAD_LIST_ID = 'channels:unread'; + const requestOptions: ChannelPaginatorRequestOptions = { presence: true, state: true, @@ -370,7 +375,7 @@ const App = () => { fallback.setItems({ isLastPage: true, valueOrFactory: [] }); // One state update for the whole set — inserting them one by one would publish (and re-render) - // four times. + // once per list. channelManager.setPaginators([ new ChannelPaginator({ client: chatClient, @@ -380,6 +385,15 @@ const App = () => { requestOptions, sort, }), + new ChannelPaginator({ + client: chatClient, + // `has_unread: false` is rejected by the API, so there is no "all read" counterpart + filters: { ...filters, archived: false, has_unread: true, muted: false }, + id: UNREAD_LIST_ID, + paginatorOptions: { pageSize: CHANNELS_PAGE_SIZE }, + requestOptions, + sort, + }), new ChannelPaginator({ client: chatClient, filters: { ...filters, archived: true }, @@ -395,13 +409,27 @@ const App = () => { fallback, ]); - channelManager.setOwnershipResolver([ + // Ownership is exclusive, so the unread list has to be granted outside the priority order — + // ranked, it would steal every unread channel out of "My channels"; unranked, the priority + // winner would evict it from the unread list instead. + const byPriority = createPriorityOwnershipResolver([ 'channels:archived', 'channels:muted', 'channels:default', 'channels:opened', ]); + channelManager.setOwnershipResolver((params) => { + const buckets = params.matchingPaginators.filter( + (paginator) => paginator.id !== UNREAD_LIST_ID, + ); + const owners = byPriority({ ...params, matchingPaginators: buckets }); + + return buckets.length === params.matchingPaginators.length + ? owners + : [...owners, UNREAD_LIST_ID]; + }); + return () => { // this app is the only one registering lists on the manager, so it can drop them all at once channelManager.clearPaginators(); diff --git a/examples/vite/src/ChatLayout/SwitchableChannelNavigation.tsx b/examples/vite/src/ChatLayout/SwitchableChannelNavigation.tsx index 3f07aaf14..cf0f8dceb 100644 --- a/examples/vite/src/ChatLayout/SwitchableChannelNavigation.tsx +++ b/examples/vite/src/ChatLayout/SwitchableChannelNavigation.tsx @@ -32,6 +32,7 @@ import { ScrollToActiveChannelButton } from './ScrollToActiveChannelButton'; // this map lives here in the example rather than in the SDK. const CHANNEL_LIST_LABELS: Record = { 'channels:default': 'My channels', + 'channels:unread': 'Unread', 'channels:archived': 'Archived', 'channels:muted': 'Muted', 'channels:opened': 'Opened',