Skip to content

Commit

Permalink
Merge branch 'master' into feat/search-channel-by-message-text
Browse files Browse the repository at this point in the history
# Conflicts:
#	package.json
#	yarn.lock
  • Loading branch information
MartinCupela committed Feb 12, 2025
2 parents ed3f7da + 1c445de commit 422600f
Show file tree
Hide file tree
Showing 13 changed files with 203 additions and 56 deletions.
26 changes: 26 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,29 @@
## [12.11.1](https://github.com/GetStream/stream-chat-react/compare/v12.11.0...v12.11.1) (2025-02-11)


### Bug Fixes

* **handleMemberUpdated:** consider both pinned and archived channels ([#2638](https://github.com/GetStream/stream-chat-react/issues/2638)) ([ae66de8](https://github.com/GetStream/stream-chat-react/commit/ae66de8d3c5ed78fbe5aa5388ebf8a1b8e90a9e9))

## [12.11.0](https://github.com/GetStream/stream-chat-react/compare/v12.10.0...v12.11.0) (2025-02-07)


### Bug Fixes

* change useStateStore to use useSyncExternalStore ([#2573](https://github.com/GetStream/stream-chat-react/issues/2573)) ([6f2de4e](https://github.com/GetStream/stream-chat-react/commit/6f2de4ebd8e8a5b9668b44bb0204ea695c48ff01))
* make `channel.visible` respect archived and pinned channels ([#2633](https://github.com/GetStream/stream-chat-react/issues/2633)) ([2d2e2e5](https://github.com/GetStream/stream-chat-react/commit/2d2e2e56e6aff92a00d3dd2e6da8c5f7b64698e1))


### Features

* allow custom ReactionsListModal ([#2632](https://github.com/GetStream/stream-chat-react/issues/2632)) ([a428dc9](https://github.com/GetStream/stream-chat-react/commit/a428dc938388947b1f7e51fde3dd579eba76ea2f))
* allow to search for channels only ([#2625](https://github.com/GetStream/stream-chat-react/issues/2625)) ([a4d6d83](https://github.com/GetStream/stream-chat-react/commit/a4d6d83cf7c63f3284f0e0c3d0689f64ae725323))


### Chores

* **deps:** upgrade @stream-io/stream-chat-css to version 5.7.0 ([#2636](https://github.com/GetStream/stream-chat-react/issues/2636)) ([8b7cfba](https://github.com/GetStream/stream-chat-react/commit/8b7cfba7140588f5fd1d727b48b7e6c7da5d99fe))

## [12.10.0](https://github.com/GetStream/stream-chat-react/compare/v12.9.0...v12.10.0) (2025-01-28)


Expand Down
3 changes: 3 additions & 0 deletions src/components/Channel/Channel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ type ChannelPropsForwardedToComponentContext<
| 'reactionOptions'
| 'ReactionSelector'
| 'ReactionsList'
| 'ReactionsListModal'
| 'SendButton'
| 'StartRecordingAudioButton'
| 'ThreadHead'
Expand Down Expand Up @@ -1358,6 +1359,7 @@ const ChannelInner = <
reactionOptions: props.reactionOptions,
ReactionSelector: props.ReactionSelector,
ReactionsList: props.ReactionsList,
ReactionsListModal: props.ReactionsListModal,
SendButton: props.SendButton,
StartRecordingAudioButton: props.StartRecordingAudioButton,
StopAIGenerationButton: props.StopAIGenerationButton,
Expand Down Expand Up @@ -1417,6 +1419,7 @@ const ChannelInner = <
props.QuotedPoll,
props.ReactionSelector,
props.ReactionsList,
props.ReactionsListModal,
props.SendButton,
props.StartRecordingAudioButton,
props.ThreadHead,
Expand Down
47 changes: 33 additions & 14 deletions src/components/ChannelList/hooks/useChannelListShape.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import { Dispatch, SetStateAction, useCallback, useEffect, useMemo, useRef } from 'react';
import { Channel, Event, ExtendableGenerics } from 'stream-chat';
import uniqBy from 'lodash.uniqby';

import {
extractSortValue,
Expand Down Expand Up @@ -68,7 +67,9 @@ type HandleChannelHiddenParameters<SCG extends ExtendableGenerics> = BaseParamet
RepeatedParameters<SCG>;

type HandleChannelVisibleParameters<SCG extends ExtendableGenerics> =
BaseParameters<SCG> & RepeatedParameters<SCG>;
BaseParameters<SCG> &
RepeatedParameters<SCG> &
Required<Pick<ChannelListProps<SCG>, 'sort' | 'filters'>>;

type HandleChannelTruncatedParameters<SCG extends ExtendableGenerics> =
BaseParameters<SCG> & RepeatedParameters<SCG>;
Expand Down Expand Up @@ -194,7 +195,6 @@ export const useChannelListShapeDefaults = <SCG extends ExtendableGenerics>() =>
moveChannelUpwards({
channels,
channelToMove: channel,
channelToMoveIndexWithinChannels: -1,
sort,
}),
);
Expand Down Expand Up @@ -239,7 +239,6 @@ export const useChannelListShapeDefaults = <SCG extends ExtendableGenerics>() =>
moveChannelUpwards({
channels,
channelToMove: channel,
channelToMoveIndexWithinChannels: -1,
sort,
}),
);
Expand Down Expand Up @@ -286,6 +285,11 @@ export const useChannelListShapeDefaults = <SCG extends ExtendableGenerics>() =>
const considerPinnedChannels = shouldConsiderPinnedChannels(sort);
const considerArchivedChannels = shouldConsiderArchivedChannels(filters);

// `pinned_at` nor `archived` properties are set or channel list order is locked, return early
if ((!considerPinnedChannels && !considerArchivedChannels) || lockChannelOrder) {
return;
}

const pinnedAtSort = extractSortValue({ atIndex: 0, sort, targetKey: 'pinned_at' });

setChannels((currentChannels) => {
Expand All @@ -297,9 +301,6 @@ export const useChannelListShapeDefaults = <SCG extends ExtendableGenerics>() =>
const isTargetChannelArchived = isChannelArchived(targetChannel);
const isTargetChannelPinned = isChannelPinned(targetChannel);

// handle pinning
if (!considerPinnedChannels || lockChannelOrder) return currentChannels;

const newChannels = [...currentChannels];

if (targetChannelExistsWithinList) {
Expand Down Expand Up @@ -353,20 +354,36 @@ export const useChannelListShapeDefaults = <SCG extends ExtendableGenerics>() =>
async ({
customHandler,
event,
filters,
setChannels,
sort,
}: HandleChannelVisibleParameters<SCG>) => {
if (typeof customHandler === 'function') {
return customHandler(setChannels, event);
}

if (event.type && event.channel_type && event.channel_id) {
const channel = await getChannel({
client,
id: event.channel_id,
type: event.channel_type,
});
setChannels((channels) => uniqBy([channel, ...channels], 'cid'));
if (!event.channel) {
return;
}

const channel = await getChannel({
client,
id: event.channel.id,
type: event.channel.type,
});

const considerArchivedChannels = shouldConsiderArchivedChannels(filters);
if (isChannelArchived(channel) && considerArchivedChannels && !filters.archived) {
return;
}

setChannels((channels) =>
moveChannelUpwards({
channels,
channelToMove: channel,
sort,
}),
);
},
[client],
);
Expand Down Expand Up @@ -586,7 +603,9 @@ export const usePrepareShapeHandlers = <SCG extends ExtendableGenerics>({
defaults.handleChannelVisible({
customHandler: onChannelVisible,
event,
filters,
setChannels,
sort,
});
break;
case 'channel.truncated':
Expand Down
15 changes: 6 additions & 9 deletions src/components/MediaRecorder/classes/MediaRecorderController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,25 +17,18 @@ import {
} from '../../ReactFileUtilities';
import { TranslationContextValue } from '../../../context';
import { defaultTranslatorFunction } from '../../../i18n';
import { isSafari } from '../../../utils/browsers';
import { mergeDeepUndefined } from '../../../utils/mergeDeep';

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

const RECORDED_MIME_TYPE_BY_BROWSER = {
export const RECORDED_MIME_TYPE_BY_BROWSER = {
audio: {
others: 'audio/webm',
safari: 'audio/mp4;codecs=mp4a.40.2',
},
} as const;

export const DEFAULT_MEDIA_RECORDER_CONFIG: MediaRecorderConfig = {
mimeType: isSafari()
? RECORDED_MIME_TYPE_BY_BROWSER.audio.safari
: RECORDED_MIME_TYPE_BY_BROWSER.audio.others,
} as const;

export const DEFAULT_AUDIO_TRANSCODER_CONFIG: TranscoderConfig = {
sampleRate: 16000,
} as const;
Expand Down Expand Up @@ -120,7 +113,11 @@ export class MediaRecorderController<

this.mediaRecorderConfig = mergeDeepUndefined(
{ ...config?.mediaRecorderConfig },
DEFAULT_MEDIA_RECORDER_CONFIG,
{
mimeType: MediaRecorder.isTypeSupported('audio/webm')
? RECORDED_MIME_TYPE_BY_BROWSER.audio.others
: RECORDED_MIME_TYPE_BY_BROWSER.audio.safari,
},
);

this.transcoderConfig = mergeDeepUndefined(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import * as transcoder from '../../transcode';
import * as wavTranscoder from '../../transcode/wav';
import {
DEFAULT_AUDIO_TRANSCODER_CONFIG,
DEFAULT_MEDIA_RECORDER_CONFIG,
MediaRecorderController,
MediaRecordingState,
RECORDED_MIME_TYPE_BY_BROWSER,
RecordingAttachmentType,
} from '../MediaRecorderController';
import {
Expand Down Expand Up @@ -92,10 +92,27 @@ describe('MediaRecorderController', () => {
});
afterEach(jest.clearAllMocks);

it('provides defaults on initiation', () => {
it('provides defaults on initiation (non-Safari)', () => {
const controller = new MediaRecorderController();
expect(controller.mediaRecorderConfig).toStrictEqual(
expect.objectContaining(DEFAULT_MEDIA_RECORDER_CONFIG),
expect.objectContaining({ mimeType: RECORDED_MIME_TYPE_BY_BROWSER.audio.others }),
);
expect(controller.transcoderConfig).toStrictEqual(
expect.objectContaining(DEFAULT_AUDIO_TRANSCODER_CONFIG),
);
expect(controller.amplitudeRecorderConfig).toStrictEqual(
expect.objectContaining(DEFAULT_AMPLITUDE_RECORDER_CONFIG),
);
expect(controller.t).toStrictEqual(defaultTranslatorFunction);
expect(controller.mediaType).toStrictEqual('audio');
expect(controller.customGenerateRecordingTitle).toBeUndefined();
});

it('provides defaults on initiation (Safari)', () => {
MediaRecorder.isTypeSupported.mockReturnValueOnce(false);
const controller = new MediaRecorderController();
expect(controller.mediaRecorderConfig).toStrictEqual(
expect.objectContaining({ mimeType: RECORDED_MIME_TYPE_BY_BROWSER.audio.safari }),
);
expect(controller.transcoderConfig).toStrictEqual(
expect.objectContaining(DEFAULT_AUDIO_TRANSCODER_CONFIG),
Expand Down Expand Up @@ -151,7 +168,7 @@ describe('MediaRecorderController', () => {
const controller = new MediaRecorderController({ generateRecordingTitle });
expect(controller.customGenerateRecordingTitle).toStrictEqual(generateRecordingTitle);
expect(controller.mediaRecorderConfig).toStrictEqual(
expect.objectContaining(DEFAULT_MEDIA_RECORDER_CONFIG),
expect.objectContaining({ mimeType: RECORDED_MIME_TYPE_BY_BROWSER.audio.others }),
);
expect(controller.transcoderConfig).toStrictEqual(
expect.objectContaining(DEFAULT_AUDIO_TRANSCODER_CONFIG),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ import { TranslationProvider } from '../../../../context';
import { renderHook } from '@testing-library/react';
import React from 'react';
import { useMediaRecorder } from '../useMediaRecorder';
import { EventEmitterMock } from '../../../../mock-builders/browser';
import { EventEmitterMock, MediaRecorderMock } from '../../../../mock-builders/browser';
import { act } from '@testing-library/react';
import { DEFAULT_AMPLITUDE_RECORDER_CONFIG } from '../../classes/AmplitudeRecorder';
import { DEFAULT_AUDIO_TRANSCODER_CONFIG } from '../../classes';
import { generateVoiceRecordingAttachment } from '../../../../mock-builders';

window.MediaRecorder = MediaRecorderMock;

const handleSubmit = jest.fn();
const uploadAttachment = jest.fn();

Expand All @@ -27,8 +29,8 @@ const render = async (params = {}) => {
<TranslationProvider value={translationContext}>{children}</TranslationProvider>
);
let result;
await act(() => {
result = renderHook(() => useMediaRecorder({ enabled: true, ...params }), {
await act(async () => {
result = await renderHook(() => useMediaRecorder({ enabled: true, ...params }), {
wrapper,
});
});
Expand Down
39 changes: 30 additions & 9 deletions src/components/Reactions/ReactionsList.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,27 @@
import React, { useState } from 'react';
import clsx from 'clsx';

import type { ReactionGroupResponse, ReactionResponse } from 'stream-chat';

import {
ReactionsListModal as DefaultReactionsListModal,
ReactionsListModalProps,
} from './ReactionsListModal';
import { useProcessReactions } from './hooks/useProcessReactions';
import {
MessageContextValue,
useComponentContext,
useTranslationContext,
} from '../../context';

import { MAX_MESSAGE_REACTIONS_TO_FETCH } from '../Message/hooks';

import type { ReactionGroupResponse, ReactionResponse, ReactionSort } from 'stream-chat';
import type { DefaultStreamChatGenerics } from '../../types/types';
import type { ReactionOptions } from './reactionOptions';
import type {
ReactionDetailsComparator,
ReactionsComparator,
ReactionType,
} from './types';
import { ReactionsListModal } from './ReactionsListModal';
import { MessageContextValue, useTranslationContext } from '../../context';
import { MAX_MESSAGE_REACTIONS_TO_FETCH } from '../Message/hooks';

export type ReactionsListProps<
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics,
Expand Down Expand Up @@ -44,7 +52,7 @@ export type ReactionsListProps<
/** Comparator function to sort the list of reacted users
* @deprecated use `reactionDetailsSort` instead
*/
sortReactionDetails?: ReactionDetailsComparator;
sortReactionDetails?: ReactionDetailsComparator<StreamChatGenerics>;
/** Comparator function to sort reactions, defaults to chronological order */
sortReactions?: ReactionsComparator;
};
Expand All @@ -67,6 +75,7 @@ const UnMemoizedReactionsList = <
const [selectedReactionType, setSelectedReactionType] =
useState<ReactionType<StreamChatGenerics> | null>(null);
const { t } = useTranslationContext('ReactionsList');
const { ReactionsListModal = DefaultReactionsListModal } = useComponentContext();

const handleReactionButtonClick = (reactionType: string) => {
if (totalReactionCount > MAX_MESSAGE_REACTIONS_TO_FETCH) {
Expand Down Expand Up @@ -126,13 +135,25 @@ const UnMemoizedReactionsList = <
</div>
{selectedReactionType !== null && (
<ReactionsListModal
handleFetchReactions={handleFetchReactions}
handleFetchReactions={
handleFetchReactions as (
reactionType?: string,
sort?: ReactionSort<StreamChatGenerics>,
) => Promise<Array<ReactionResponse<StreamChatGenerics>>>
}
onClose={() => setSelectedReactionType(null)}
onSelectedReactionTypeChange={setSelectedReactionType}
onSelectedReactionTypeChange={
setSelectedReactionType as ReactionsListModalProps['onSelectedReactionTypeChange']
}
open={selectedReactionType !== null}
reactions={existingReactions}
selectedReactionType={selectedReactionType}
sortReactionDetails={sortReactionDetails}
sortReactionDetails={
sortReactionDetails as (
a: ReactionResponse<StreamChatGenerics>,
b: ReactionResponse<StreamChatGenerics>,
) => number
}
/>
)}
</>
Expand Down
2 changes: 1 addition & 1 deletion src/components/Reactions/ReactionsListModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { MessageContextValue, useMessageContext } from '../../context';
import { DefaultStreamChatGenerics } from '../../types/types';
import { ReactionSort } from 'stream-chat';

type ReactionsListModalProps<
export type ReactionsListModalProps<
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics,
> = ModalProps &
Partial<
Expand Down
Loading

0 comments on commit 422600f

Please sign in to comment.