UNPKG

stream-chat-react

Version:

React components to create chat conversations or livestream style chat

159 lines (158 loc) 9.77 kB
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 })));