stream-chat-react
Version:
React components to create chat conversations or livestream style chat
159 lines (158 loc) • 9.77 kB
JavaScript
import React, { useLayoutEffect, useRef, useState } from 'react';
import ReactPlayer from 'react-player';
import clsx from 'clsx';
import * as linkify from 'linkifyjs';
import { isSharedLocationResponse } from 'stream-chat';
import { AttachmentActions as DefaultAttachmentActions } from './AttachmentActions';
import { Audio as DefaultAudio } from './Audio';
import { VoiceRecording as DefaultVoiceRecording } from './VoiceRecording';
import { Gallery as DefaultGallery, ImageComponent as DefaultImage } from '../Gallery';
import { Card as DefaultCard } from './Card';
import { FileAttachment as DefaultFile } from './FileAttachment';
import { Geolocation as DefaultGeolocation } from './Geolocation';
import { UnsupportedAttachment as DefaultUnsupportedAttachment } from './UnsupportedAttachment';
import { isGalleryAttachmentType, isSvgAttachment } from './utils';
import { useChannelStateContext } from '../../context/ChannelStateContext';
export const AttachmentWithinContainer = ({ attachment, children, componentType, }) => {
const isGAT = isGalleryAttachmentType(attachment);
let extra = '';
if (!isGAT && !isSharedLocationResponse(attachment)) {
extra =
componentType === 'card' && !attachment?.image_url && !attachment?.thumb_url
? 'no-image'
: attachment?.actions?.length
? 'actions'
: '';
}
const classNames = clsx('str-chat__message-attachment str-chat__message-attachment-dynamic-size', {
[`str-chat__message-attachment--${componentType}`]: componentType,
[`str-chat__message-attachment--${attachment?.type}`]: attachment?.type,
[`str-chat__message-attachment--${componentType}--${extra}`]: componentType && extra,
'str-chat__message-attachment--svg-image': isSvgAttachment(attachment),
'str-chat__message-attachment-with-actions': extra === 'actions',
});
return React.createElement("div", { className: classNames }, children);
};
export const AttachmentActionsContainer = ({ actionHandler, attachment, AttachmentActions = DefaultAttachmentActions, }) => {
if (!attachment.actions?.length)
return null;
return (React.createElement(AttachmentActions, { ...attachment, actionHandler: actionHandler, actions: attachment.actions, id: attachment.localMetadata?.id || '', text: attachment.text || '' }));
};
function getCssDimensionsVariables(url) {
const cssVars = {
'--original-height': 1000000,
'--original-width': 1000000,
};
if (linkify.test(url, 'url')) {
const urlParams = new URL(url).searchParams;
const oh = Number(urlParams.get('oh'));
const ow = Number(urlParams.get('ow'));
const originalHeight = oh > 1 ? oh : 1000000;
const originalWidth = ow > 1 ? ow : 1000000;
cssVars['--original-width'] = originalWidth;
cssVars['--original-height'] = originalHeight;
}
return cssVars;
}
export const GalleryContainer = ({ attachment, Gallery = DefaultGallery, }) => {
const imageElements = useRef([]);
const { imageAttachmentSizeHandler } = useChannelStateContext();
const [attachmentConfigurations, setAttachmentConfigurations] = useState([]);
useLayoutEffect(() => {
if (!imageElements.current || !imageAttachmentSizeHandler)
return;
const newConfigurations = [];
const nonNullImageElements = imageElements.current.filter((e) => !!e);
if (nonNullImageElements.length < imageElements.current.length) {
imageElements.current = nonNullImageElements;
}
imageElements.current.forEach((element, i) => {
if (!element)
return;
const config = imageAttachmentSizeHandler(attachment.images[i], element);
newConfigurations.push(config);
});
setAttachmentConfigurations(newConfigurations);
}, [imageAttachmentSizeHandler, attachment]);
const images = attachment.images.map((image, i) => ({
...image,
previewUrl: attachmentConfigurations[i]?.url || 'about:blank',
style: getCssDimensionsVariables(attachment.images[i]?.image_url || attachment.images[i]?.thumb_url || ''),
}));
return (React.createElement(AttachmentWithinContainer, { attachment: attachment, componentType: 'gallery' },
React.createElement(Gallery, { images: images || [], innerRefs: imageElements, key: 'gallery' })));
};
export const ImageContainer = (props) => {
const { attachment, Image = DefaultImage } = props;
const componentType = 'image';
const imageElement = useRef(null);
const { imageAttachmentSizeHandler } = useChannelStateContext();
const [attachmentConfiguration, setAttachmentConfiguration] = useState(undefined);
useLayoutEffect(() => {
if (imageElement.current && imageAttachmentSizeHandler) {
const config = imageAttachmentSizeHandler(attachment, imageElement.current);
setAttachmentConfiguration(config);
}
}, [imageElement, imageAttachmentSizeHandler, attachment]);
const imageConfig = {
...attachment,
previewUrl: attachmentConfiguration?.url || 'about:blank',
style: getCssDimensionsVariables(attachment.image_url || attachment.thumb_url || ''),
};
if (attachment.actions && attachment.actions.length) {
return (React.createElement(AttachmentWithinContainer, { attachment: attachment, componentType: componentType },
React.createElement("div", { className: 'str-chat__attachment' },
React.createElement(Image, { ...imageConfig, innerRef: imageElement }),
React.createElement(AttachmentActionsContainer, { ...props }))));
}
return (React.createElement(AttachmentWithinContainer, { attachment: attachment, componentType: componentType },
React.createElement(Image, { ...imageConfig, innerRef: imageElement })));
};
export const CardContainer = (props) => {
const { attachment, Card = DefaultCard } = props;
const componentType = 'card';
if (attachment.actions && attachment.actions.length) {
return (React.createElement(AttachmentWithinContainer, { attachment: attachment, componentType: componentType },
React.createElement("div", { className: 'str-chat__attachment' },
React.createElement(Card, { ...attachment }),
React.createElement(AttachmentActionsContainer, { ...props }))));
}
return (React.createElement(AttachmentWithinContainer, { attachment: attachment, componentType: componentType },
React.createElement(Card, { ...attachment })));
};
export const FileContainer = ({ attachment, File = DefaultFile, }) => {
if (!attachment.asset_url)
return null;
return (React.createElement(AttachmentWithinContainer, { attachment: attachment, componentType: 'file' },
React.createElement(File, { attachment: attachment })));
};
export const AudioContainer = ({ attachment, Audio = DefaultAudio, }) => (React.createElement(AttachmentWithinContainer, { attachment: attachment, componentType: 'audio' },
React.createElement("div", { className: 'str-chat__attachment' },
React.createElement(Audio, { og: attachment }))));
export const VoiceRecordingContainer = ({ attachment, isQuoted, VoiceRecording = DefaultVoiceRecording, }) => (React.createElement(AttachmentWithinContainer, { attachment: attachment, componentType: 'voiceRecording' },
React.createElement("div", { className: 'str-chat__attachment' },
React.createElement(VoiceRecording, { attachment: attachment, isQuoted: isQuoted }))));
export const MediaContainer = (props) => {
const { attachment, Media = ReactPlayer } = props;
const componentType = 'media';
const { shouldGenerateVideoThumbnail, videoAttachmentSizeHandler } = useChannelStateContext();
const videoElement = useRef(null);
const [attachmentConfiguration, setAttachmentConfiguration] = useState();
useLayoutEffect(() => {
if (videoElement.current && videoAttachmentSizeHandler) {
const config = videoAttachmentSizeHandler(attachment, videoElement.current, shouldGenerateVideoThumbnail);
setAttachmentConfiguration(config);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [videoElement, videoAttachmentSizeHandler, attachment]);
const content = (React.createElement("div", { className: 'str-chat__player-wrapper', "data-testid": 'video-wrapper', ref: videoElement, style: getCssDimensionsVariables(attachment.thumb_url || '') },
React.createElement(Media, { className: 'react-player', config: { file: { attributes: { poster: attachmentConfiguration?.thumbUrl } } }, controls: true, height: '100%', url: attachmentConfiguration?.url, width: '100%' })));
return attachment.actions?.length ? (React.createElement(AttachmentWithinContainer, { attachment: attachment, componentType: componentType },
React.createElement("div", { className: 'str-chat__attachment' },
content,
React.createElement(AttachmentActionsContainer, { ...props })))) : (React.createElement(AttachmentWithinContainer, { attachment: attachment, componentType: componentType }, content));
};
export const GeolocationContainer = ({ Geolocation = DefaultGeolocation, location, }) => (React.createElement(AttachmentWithinContainer, { attachment: location, componentType: 'geolocation' },
React.createElement(Geolocation, { location: location })));
export const UnsupportedAttachmentContainer = ({ attachment, UnsupportedAttachment = DefaultUnsupportedAttachment, }) => (React.createElement(React.Fragment, null,
React.createElement(UnsupportedAttachment, { attachment: attachment })));