Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add channel messages pagination indicators #1332

Merged
merged 12 commits into from
Aug 22, 2024
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion src/channel.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ChannelState } from './channel_state';
import { logChatPromiseExecution, normalizeQuerySort } from './utils';
import {logChatPromiseExecution, messageSetPagination, normalizeQuerySort} from './utils';
import { StreamChat } from './client';
import {
APIResponse,
@@ -58,6 +58,7 @@ import {
AscDesc,
} from './types';
import { Role } from './permissions';
import {DEFAULT_QUERY_CHANNEL_MESSAGE_LIST_PAGE_SIZE} from "./constants";

/**
* Channel - The Channel class manages it's own state.
@@ -1044,6 +1045,10 @@ export class Channel<StreamChatGenerics extends ExtendableGenerics = DefaultGene

// add any messages to our channel state
const { messageSet } = this._initializeState(state, messageSetToAddToIfDoesNotExist);
messageSet.pagination = {
...messageSet.pagination,
...messageSetPagination({messagePaginationOptions: options.messages, requestedPageSize: options.messages?.limit ?? DEFAULT_QUERY_CHANNEL_MESSAGE_LIST_PAGE_SIZE, returnedPageSize: state.messages.length})
};

const areCapabilitiesChanged =
[...(state.channel.own_capabilities || [])].sort().join() !==
16 changes: 9 additions & 7 deletions src/channel_state.ts
Original file line number Diff line number Diff line change
@@ -6,6 +6,7 @@ import {
ExtendableGenerics,
FormatMessageResponse,
MessageResponse,
MessageSet,
MessageSetType,
PendingMessageResponse,
PollResponse,
@@ -56,11 +57,7 @@ export class ChannelState<StreamChatGenerics extends ExtendableGenerics = Defaul
* The state manages these lists and merges them when lists overlap
* The messages array contains the currently active set
*/
messageSets: {
isCurrent: boolean;
isLatest: boolean;
messages: Array<ReturnType<ChannelState<StreamChatGenerics>['formatMessage']>>;
}[] = [];
messageSets: MessageSet[] = [];
constructor(channel: Channel<StreamChatGenerics>) {
this._channel = channel;
this.watcher_count = 0;
@@ -108,6 +105,10 @@ export class ChannelState<StreamChatGenerics extends ExtendableGenerics = Defaul
this.messageSets[index].messages = messages;
}

get messagePagination() {
return this.messageSets.find((s) => s.isCurrent)?.pagination || {};
}

/**
* addMessageSorted - Add a message to the state
*
@@ -717,14 +718,15 @@ export class ChannelState<StreamChatGenerics extends ExtendableGenerics = Defaul
}

initMessages() {
this.messageSets = [{ messages: [], isLatest: true, isCurrent: true }];
this.messageSets = [{ messages: [], isLatest: true, isCurrent: true, pagination: {} }];
}

/**
* loadMessageIntoState - Loads a given message (and messages around it) into the state
*
* @param {string} messageId The id of the message, or 'latest' to indicate switching to the latest messages
* @param {string} parentMessageId The id of the parent message, if we want load a thread reply
* @param {number} limit The page size if the message has to be queried from the server
*/
async loadMessageIntoState(messageId: string | 'latest', parentMessageId?: string, limit = 25) {
let messageSetIndex: number;
@@ -820,7 +822,7 @@ export class ChannelState<StreamChatGenerics extends ExtendableGenerics = Defaul
targetMessageSetIndex = overlappingMessageSetIndices[0];
// No new message set is created if newMessages only contains thread replies
} else if (newMessages.some((m) => !m.parent_id)) {
this.messageSets.push({ messages: [], isCurrent: false, isLatest: false });
this.messageSets.push({ messages: [], isCurrent: false, isLatest: false, pagination: {} });
targetMessageSetIndex = this.messageSets.length - 1;
}
break;
25 changes: 17 additions & 8 deletions src/client.ts
Original file line number Diff line number Diff line change
@@ -21,6 +21,7 @@ import {
isFunction,
isOnline,
isOwnUserBaseProperty,
messageSetPagination,
normalizeQuerySort,
randomId,
retryInterval,
@@ -207,6 +208,7 @@ import {
import { InsightMetrics, postInsights } from './insights';
import { Thread } from './thread';
import { Moderation } from './moderation';
import {DEFAULT_QUERY_CHANNELS_MESSAGE_LIST_PAGE_SIZE} from "./constants";

function isString(x: unknown): x is string {
return typeof x === 'string' || x instanceof String;
@@ -1601,7 +1603,7 @@ export class StreamChat<StreamChatGenerics extends ExtendableGenerics = DefaultG
},
});

return this.hydrateActiveChannels(data.channels, stateOptions);
return this.hydrateActiveChannels(data.channels, stateOptions, options);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spaghetti code architecture forced me to do this.

}

/**
@@ -1638,26 +1640,33 @@ export class StreamChat<StreamChatGenerics extends ExtendableGenerics = DefaultG
hydrateActiveChannels(
channelsFromApi: ChannelAPIResponse<StreamChatGenerics>[] = [],
stateOptions: ChannelStateOptions = {},
queryChannelsOptions?: ChannelOptions,
) {
const { skipInitialization, offlineMode = false } = stateOptions;

for (const channelState of channelsFromApi) {
this._addChannelConfig(channelState.channel);
}

const channels: Channel<StreamChatGenerics>[] = [];

for (const channelState of channelsFromApi) {
this._addChannelConfig(channelState.channel);
const c = this.channel(channelState.channel.type, channelState.channel.id);
c.data = channelState.channel;
c.offlineMode = offlineMode;
c.initialized = !offlineMode;

let updatedMessagesSet;
if (skipInitialization === undefined) {
c._initializeState(channelState, 'latest');
const { messageSet} = c._initializeState(channelState, 'latest');
updatedMessagesSet = messageSet;
} else if (!skipInitialization.includes(channelState.channel.id)) {
c.state.clearMessages();
c._initializeState(channelState, 'latest');
const { messageSet} = c._initializeState(channelState, 'latest');
updatedMessagesSet = messageSet;
}

if (updatedMessagesSet) {
updatedMessagesSet.pagination = {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pagination indicators are calculated once the requested page is merged into the parent message set.

...updatedMessagesSet.pagination,
...messageSetPagination({requestedPageSize: queryChannelsOptions?.message_limit || DEFAULT_QUERY_CHANNELS_MESSAGE_LIST_PAGE_SIZE, returnedPageSize: channelState.messages.length})
};
}

channels.push(c);
2 changes: 2 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const DEFAULT_QUERY_CHANNELS_MESSAGE_LIST_PAGE_SIZE = 25;
export const DEFAULT_QUERY_CHANNEL_MESSAGE_LIST_PAGE_SIZE = 100;
6 changes: 6 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -2900,6 +2900,12 @@ export type ImportTask = {
};

export type MessageSetType = 'latest' | 'current' | 'new';
export type MessageSet<StreamChatGenerics extends ExtendableGenerics = DefaultGenerics> = {
isCurrent: boolean;
isLatest: boolean;
messages: FormatMessageResponse<StreamChatGenerics>[];
pagination: { hasNext?:boolean; hasPrev?: boolean };
}

export type PushProviderUpsertResponse = {
push_provider: PushProvider;
18 changes: 18 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -9,6 +9,8 @@ import {
MessageResponse,
FormatMessageResponse,
ReactionGroupResponse,
MessageSet,
MessagePaginationOptions,
} from './types';
import { AxiosRequestConfig } from 'axios';

@@ -396,3 +398,19 @@ function maybeGetReactionGroupsFallback(

return null;
}

export const messageSetPagination = ({returnedPageSize, messagePaginationOptions, requestedPageSize}: {requestedPageSize: number, returnedPageSize: number, messagePaginationOptions?: MessagePaginationOptions;}) => {
const queriedNextMessages = messagePaginationOptions && (messagePaginationOptions.created_at_after_or_equal || messagePaginationOptions.created_at_after || messagePaginationOptions.id_gt || messagePaginationOptions.id_gte);
const queriedPrevMessages = !messagePaginationOptions || (messagePaginationOptions.created_at_before_or_equal || messagePaginationOptions.created_at_before || messagePaginationOptions.id_lt || messagePaginationOptions.id_lte);

const pagination: MessageSet['pagination'] = {};
const hasMore = returnedPageSize >= requestedPageSize;

if (typeof queriedPrevMessages !== 'undefined') {
pagination.hasPrev = hasMore;
}
if (typeof queriedNextMessages !== 'undefined') {
pagination.hasNext = hasMore;
}
return pagination;
};