stream-chat-react
Version:
React components to create chat conversations or livestream style chat
198 lines (197 loc) • 14.5 kB
JavaScript
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
import React, { useCallback, useEffect, useRef, useState } from 'react';
import clsx from 'clsx';
import { ChannelListMessenger } 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 { usePaginatedChannels } from './hooks/usePaginatedChannels';
import { useUserPresenceChangedListener } from './hooks/useUserPresenceChangedListener';
import { MAX_QUERY_CHANNELS_LIMIT, moveChannelUp } from './utils';
import { Avatar as DefaultAvatar } from '../Avatar/Avatar';
import { ChannelPreview } from '../ChannelPreview/ChannelPreview';
import { ChannelSearch as DefaultChannelSearch, } from '../ChannelSearch/ChannelSearch';
import { ChatDown } from '../ChatDown/ChatDown';
import { EmptyStateIndicator as DefaultEmptyStateIndicator, } from '../EmptyStateIndicator';
import { LoadingChannels } from '../Loading/LoadingChannels';
import { LoadMorePaginator } from '../LoadMore/LoadMorePaginator';
import { useChatContext } from '../../context/ChatContext';
var DEFAULT_FILTERS = {};
var DEFAULT_OPTIONS = {};
var DEFAULT_SORT = {};
var UnMemoizedChannelList = function (props) {
var additionalChannelSearchProps = props.additionalChannelSearchProps, _a = props.Avatar, Avatar = _a === void 0 ? DefaultAvatar : _a, allowNewMessagesFromUnfilteredChannels = props.allowNewMessagesFromUnfilteredChannels, channelRenderFilterFn = props.channelRenderFilterFn, _b = props.ChannelSearch, ChannelSearch = _b === void 0 ? DefaultChannelSearch : _b, customActiveChannel = props.customActiveChannel, _c = props.EmptyStateIndicator, EmptyStateIndicator = _c === void 0 ? DefaultEmptyStateIndicator : _c, filters = props.filters, _d = props.LoadingErrorIndicator, LoadingErrorIndicator = _d === void 0 ? ChatDown : _d, _e = props.LoadingIndicator, LoadingIndicator = _e === void 0 ? LoadingChannels : _e, _f = props.List, List = _f === void 0 ? ChannelListMessenger : _f, lockChannelOrder = props.lockChannelOrder, onAddedToChannel = props.onAddedToChannel, onChannelDeleted = props.onChannelDeleted, onChannelHidden = props.onChannelHidden, onChannelTruncated = props.onChannelTruncated, onChannelUpdated = props.onChannelUpdated, onChannelVisible = props.onChannelVisible, onMessageNew = props.onMessageNew, onRemovedFromChannel = props.onRemovedFromChannel, options = props.options, _g = props.Paginator, Paginator = _g === void 0 ? LoadMorePaginator : _g, Preview = props.Preview, renderChannels = props.renderChannels, _h = props.sendChannelsToList, sendChannelsToList = _h === void 0 ? false : _h, _j = props.setActiveChannelOnMount, setActiveChannelOnMount = _j === void 0 ? true : _j, _k = props.showChannelSearch, showChannelSearch = _k === void 0 ? false : _k, _l = props.sort, sort = _l === void 0 ? DEFAULT_SORT : _l, _m = props.watchers, watchers = _m === void 0 ? {} : _m;
var _o = useChatContext('ChannelList'), channel = _o.channel, channelsQueryState = _o.channelsQueryState, client = _o.client, closeMobileNav = _o.closeMobileNav, customClasses = _o.customClasses, _p = _o.navOpen, navOpen = _p === void 0 ? false : _p, setActiveChannel = _o.setActiveChannel, theme = _o.theme, useImageFlagEmojisOnWindows = _o.useImageFlagEmojisOnWindows;
var channelListRef = useRef(null);
var _q = useState(0), channelUpdateCount = _q[0], setChannelUpdateCount = _q[1];
var _r = useState(false), searchActive = _r[0], setSearchActive = _r[1];
/**
* Set a channel with id {customActiveChannel} as active and move it to the top of the list.
* If customActiveChannel prop is absent, then set the first channel in list as active channel.
*/
var activeChannelHandler = function (channels, setChannels) { return __awaiter(void 0, void 0, void 0, function () {
var customActiveChannelObject, newChannels;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
if (!channels.length || channels.length > ((options === null || options === void 0 ? void 0 : options.limit) || MAX_QUERY_CHANNELS_LIMIT)) {
return [2 /*return*/];
}
if (!customActiveChannel) return [3 /*break*/, 3];
customActiveChannelObject = channels.find(function (chan) { return chan.id === customActiveChannel; });
if (!!customActiveChannelObject) return [3 /*break*/, 2];
return [4 /*yield*/, client.queryChannels({ id: customActiveChannel })];
case 1:
//@ts-expect-error
customActiveChannelObject = (_a.sent())[0];
_a.label = 2;
case 2:
if (customActiveChannelObject) {
setActiveChannel(customActiveChannelObject, watchers);
newChannels = moveChannelUp({
activeChannel: customActiveChannelObject,
channels: channels,
cid: customActiveChannelObject.cid,
});
setChannels(newChannels);
}
return [2 /*return*/];
case 3:
if (setActiveChannelOnMount) {
setActiveChannel(channels[0], watchers);
}
return [2 /*return*/];
}
});
}); };
/**
* 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.
*/
var forceUpdate = function () { return setChannelUpdateCount(function (count) { return count + 1; }); };
var onSearch = useCallback(function (event) {
var _a;
if (!event.target.value) {
setSearchActive(false);
}
else {
setSearchActive(true);
}
(_a = additionalChannelSearchProps === null || additionalChannelSearchProps === void 0 ? void 0 : additionalChannelSearchProps.onSearch) === null || _a === void 0 ? void 0 : _a.call(additionalChannelSearchProps, event);
}, []);
var onSearchExit = useCallback(function () {
var _a;
setSearchActive(false);
(_a = additionalChannelSearchProps === null || additionalChannelSearchProps === void 0 ? void 0 : additionalChannelSearchProps.onSearchExit) === null || _a === void 0 ? void 0 : _a.call(additionalChannelSearchProps);
}, []);
var _s = usePaginatedChannels(client, filters || DEFAULT_FILTERS, sort || DEFAULT_SORT, options || DEFAULT_OPTIONS, activeChannelHandler), channels = _s.channels, hasNextPage = _s.hasNextPage, loadNextPage = _s.loadNextPage, setChannels = _s.setChannels;
var loadedChannels = channelRenderFilterFn ? channelRenderFilterFn(channels) : channels;
useMobileNavigation(channelListRef, navOpen, closeMobileNav);
useMessageNewListener(setChannels, lockChannelOrder, allowNewMessagesFromUnfilteredChannels);
useNotificationMessageNewListener(setChannels, onMessageNew, allowNewMessagesFromUnfilteredChannels);
useNotificationAddedToChannelListener(setChannels, onAddedToChannel, allowNewMessagesFromUnfilteredChannels);
useNotificationRemovedFromChannelListener(setChannels, onRemovedFromChannel);
useChannelDeletedListener(setChannels, onChannelDeleted);
useChannelHiddenListener(setChannels, onChannelHidden);
useChannelVisibleListener(setChannels, onChannelVisible);
useChannelTruncatedListener(setChannels, onChannelTruncated, forceUpdate);
useChannelUpdatedListener(setChannels, onChannelUpdated, forceUpdate);
useConnectionRecoveredListener(forceUpdate);
useUserPresenceChangedListener(setChannels);
useEffect(function () {
var handleEvent = function (event) {
if (event.cid === (channel === null || channel === void 0 ? void 0 : channel.cid)) {
setActiveChannel();
}
};
client.on('channel.deleted', handleEvent);
client.on('channel.hidden', handleEvent);
return function () {
client.off('channel.deleted', handleEvent);
client.off('channel.hidden', handleEvent);
};
}, [channel === null || channel === void 0 ? void 0 : channel.cid]);
var renderChannel = function (item) {
var previewProps = {
activeChannel: channel,
Avatar: Avatar,
channel: item,
// forces the update of preview component on channel update
channelUpdateCount: channelUpdateCount,
key: item.id,
Preview: Preview,
setActiveChannel: setActiveChannel,
watchers: watchers,
};
return React.createElement(ChannelPreview, __assign({}, previewProps));
};
var className = clsx('str-chat__channel-list', theme, (customClasses === null || customClasses === void 0 ? void 0 : customClasses.chat) || 'str-chat', (customClasses === null || customClasses === void 0 ? void 0 : customClasses.channelList) || 'str-chat-channel-list', {
'str-chat--windows-flags': useImageFlagEmojisOnWindows && navigator.userAgent.match(/Win/),
'str-chat-channel-list--open': navOpen,
});
var showChannelList = !searchActive || (additionalChannelSearchProps === null || additionalChannelSearchProps === void 0 ? void 0 : additionalChannelSearchProps.popupResults);
return (React.createElement(React.Fragment, null,
React.createElement("div", { className: className, ref: channelListRef },
showChannelSearch && (React.createElement(ChannelSearch, __assign({ onSearch: onSearch, onSearchExit: onSearchExit, setChannels: setChannels }, additionalChannelSearchProps))),
showChannelList && (React.createElement(List, { error: channelsQueryState.error, loadedChannels: sendChannelsToList ? loadedChannels : undefined, loading: channelsQueryState.queryInProgress === 'reload', LoadingErrorIndicator: LoadingErrorIndicator, LoadingIndicator: LoadingIndicator, setChannels: setChannels }, !(loadedChannels === null || loadedChannels === void 0 ? void 0 : loadedChannels.length) ? (React.createElement(EmptyStateIndicator, { listType: 'channel' })) : (React.createElement(Paginator, { hasNextPage: hasNextPage, loadNextPage: loadNextPage, refreshing: channelsQueryState.queryInProgress === 'load-more' }, renderChannels
? renderChannels(loadedChannels, renderChannel)
: loadedChannels.map(function (channel) { return renderChannel(channel); }))))))));
};
/**
* Renders a preview list of Channels, allowing you to select the Channel you want to open
*/
export var ChannelList = React.memo(UnMemoizedChannelList);