UNPKG

@azure/communication-react

Version:

React library for building modern communication user experiences utilizing Azure Communication Services

226 lines • 13.9 kB
// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. 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()); }); }; import { isIOS } from '@fluentui/react'; import { mergeStyles, Stack } from '@fluentui/react'; import { PersonaSize } from '@fluentui/react'; import { ErrorBar, MessageThread, TypingIndicator, useTheme } from "../../../../react-components/src"; import React, { useCallback, useEffect, useMemo } from 'react'; import { useState } from 'react'; import { AvatarPersona } from '../common/AvatarPersona'; import { useAdapter } from './adapter/ChatAdapterProvider'; import { ChatHeader, getHeaderProps } from './ChatHeader'; import { useAdaptedSelector } from './hooks/useAdaptedSelector'; import { usePropsFor } from './hooks/usePropsFor'; import { chatArea, chatContainer, chatWrapper, messageThreadChatCompositeStyles, sendboxContainerStyles, typingIndicatorContainerStyles } from './styles/Chat.styles'; import { participantListContainerPadding } from '../common/styles/ParticipantContainer.styles'; import { toFlatCommunicationIdentifier } from "../../../../acs-ui-common/src"; import { ImageOverlay } from "../../../../react-components/src"; import { SendBoxPicker } from '../common/SendBoxPicker'; import { useSelector } from './hooks/useSelector'; import { getChatMessages, getThreadId, getUserId } from './selectors/baseSelectors'; /** * @private */ export const ChatScreen = (props) => { const { onFetchAvatarPersonaData, onRenderMessage, onRenderTypingIndicator, options, styles, formFactor } = props; const defaultNumberOfChatMessagesToReload = 5; const [overlayImageItem, setOverlayImageItem] = useState(); const [isImageOverlayOpen, setIsImageOverlayOpen] = useState(false); const adapter = useAdapter(); const theme = useTheme(); useEffect(() => { // Initial data should be always fetched by the composite(or external caller) instead of the adapter const fetchData = () => __awaiter(void 0, void 0, void 0, function* () { // Fetch initial data for adapter yield adapter.fetchInitialData(); // Fetch initial set of messages. Without fetching messages here, if the Composite's adapter is changed the message thread does not load new messages. yield adapter.loadPreviousChatMessages(defaultNumberOfChatMessagesToReload); }); fetchData(); }, [adapter]); const messageThreadProps = usePropsFor(MessageThread); const typingIndicatorProps = usePropsFor(TypingIndicator); const headerProps = useAdaptedSelector(getHeaderProps); const errorBarProps = usePropsFor(ErrorBar); const overlayImageItemRef = React.useRef(overlayImageItem); overlayImageItemRef.current = overlayImageItem; const adapterChatMessages = useSelector(getChatMessages); useEffect(() => { var _a; if (overlayImageItemRef.current === undefined) { return; } const message = adapterChatMessages[overlayImageItemRef.current.messageId]; if (message === undefined) { return; } const resourceFetchResult = (_a = message.resourceCache) === null || _a === void 0 ? void 0 : _a[overlayImageItemRef.current.imageUrl]; if (overlayImageItemRef.current.imageSrc === '' && resourceFetchResult) { const fullSizeImageSrc = getResourceSourceUrl(resourceFetchResult); if (fullSizeImageSrc === undefined || fullSizeImageSrc === '' || overlayImageItemRef.current.imageSrc === fullSizeImageSrc) { return; } setOverlayImageItem(Object.assign(Object.assign({}, overlayImageItemRef.current), { imageSrc: fullSizeImageSrc })); } }, [adapterChatMessages]); const getResourceSourceUrl = (result) => { let src = ''; if (result.error || !result.sourceUrl) { src = 'blob://'; } else { src = result.sourceUrl; } return src; }; const onRenderAvatarCallback = useCallback((userId, defaultOptions) => { return React.createElement(AvatarPersona, Object.assign({ userId: userId, hidePersonaDetails: true }, defaultOptions, { dataProvider: onFetchAvatarPersonaData })); }, [onFetchAvatarPersonaData]); const messageThreadStyles = useMemo(() => { return Object.assign({}, messageThreadChatCompositeStyles(theme.semanticColors.bodyBackground), styles === null || styles === void 0 ? void 0 : styles.messageThread); }, [styles === null || styles === void 0 ? void 0 : styles.messageThread, theme.semanticColors.bodyBackground]); const typingIndicatorStyles = useMemo(() => { return Object.assign({}, styles === null || styles === void 0 ? void 0 : styles.typingIndicator); }, [styles === null || styles === void 0 ? void 0 : styles.typingIndicator]); const sendBoxStyles = useMemo(() => { return Object.assign({}, styles === null || styles === void 0 ? void 0 : styles.sendBox); }, [styles === null || styles === void 0 ? void 0 : styles.sendBox]); const userIdObject = useSelector(getUserId); const userId = toFlatCommunicationIdentifier(userIdObject); const threadId = useSelector(getThreadId); const onInlineImageClicked = useCallback((attachmentId, messageId) => { var _a, _b, _c; const message = adapterChatMessages[messageId]; const inlinedImages = (_b = (_a = message === null || message === void 0 ? void 0 : message.content) === null || _a === void 0 ? void 0 : _a.attachments) === null || _b === void 0 ? void 0 : _b.filter(attachment => { return attachment.attachmentType === 'image' && attachment.id === attachmentId; }); const attachment = inlinedImages === null || inlinedImages === void 0 ? void 0 : inlinedImages[0]; if (!attachment) { return; } let imageSrc = ''; if (attachment.url) { const resourceFetchResult = (_c = message === null || message === void 0 ? void 0 : message.resourceCache) === null || _c === void 0 ? void 0 : _c[attachment.url]; if (resourceFetchResult) { imageSrc = getResourceSourceUrl(resourceFetchResult); } else { adapter.downloadResourceToCache({ threadId, messageId, resourceUrl: attachment.url }); } } const titleIconRenderOptions = { text: message === null || message === void 0 ? void 0 : message.senderDisplayName, size: PersonaSize.size32, showOverflowTooltip: false, imageAlt: message === null || message === void 0 ? void 0 : message.senderDisplayName }; const messageSenderId = (message === null || message === void 0 ? void 0 : message.sender) !== undefined ? toFlatCommunicationIdentifier(message.sender) : userId; const titleIcon = onRenderAvatarCallback && onRenderAvatarCallback(messageSenderId, titleIconRenderOptions); const overlayImage = { title: (message === null || message === void 0 ? void 0 : message.senderDisplayName) || '', titleIcon: titleIcon, attachmentId: attachment.id, imageSrc: imageSrc, messageId: messageId, imageUrl: attachment.url || '' }; setIsImageOverlayOpen(true); setOverlayImageItem(overlayImage); }, [adapter, adapterChatMessages, onRenderAvatarCallback, userId, threadId]); const onRenderInlineImage = useCallback((inlineImage, defaultOnRender) => { var _a, _b, _c; const message = adapterChatMessages[inlineImage.messageId]; const attachment = (_b = (_a = message === null || message === void 0 ? void 0 : message.content) === null || _a === void 0 ? void 0 : _a.attachments) === null || _b === void 0 ? void 0 : _b.find(attachment => attachment.id === inlineImage.imageAttributes.id); if (attachment === undefined) { return defaultOnRender(inlineImage); } let pointerEvents = inlineImage.imageAttributes.src === '' ? 'none' : 'auto'; const resourceCache = message === null || message === void 0 ? void 0 : message.resourceCache; if (resourceCache && attachment.previewUrl && resourceCache[attachment.previewUrl] && ((_c = resourceCache[attachment.previewUrl]) === null || _c === void 0 ? void 0 : _c.error)) { pointerEvents = 'none'; } return React.createElement("span", { key: inlineImage.imageAttributes.id, onClick: () => onInlineImageClicked(inlineImage.imageAttributes.id || '', inlineImage.messageId), tabIndex: 0, role: "button", onKeyDown: e => { if (e.key === 'Enter') { onInlineImageClicked(inlineImage.imageAttributes.id || '', inlineImage.messageId); } }, style: { cursor: 'pointer', pointerEvents } }, defaultOnRender(inlineImage)); }, [adapterChatMessages, onInlineImageClicked]); const inlineImageOptions = useMemo(() => { return { onRenderInlineImage: onRenderInlineImage }; }, [onRenderInlineImage]); const onDownloadButtonClicked = useCallback((imageSrc) => { if (imageSrc === '') { return; } if (isIOS()) { window.open(imageSrc, '_blank'); } else { // Create a new anchor element const a = document.createElement('a'); // Set the href and download attributes for the anchor element a.href = imageSrc; a.download = (overlayImageItem === null || overlayImageItem === void 0 ? void 0 : overlayImageItem.attachmentId) || ''; a.rel = 'noopener noreferrer'; a.target = '_blank'; // Programmatically click the anchor element to trigger the download document.body.appendChild(a); a.click(); document.body.removeChild(a); } }, [overlayImageItem === null || overlayImageItem === void 0 ? void 0 : overlayImageItem.attachmentId]); const onSendMessageHandler = useCallback(function (content) { return __awaiter(this, void 0, void 0, function* () { yield adapter.sendMessage(content, {}); }); }, [adapter]); const onUpdateMessageHandler = useCallback(function (messageId, content) { return __awaiter(this, void 0, void 0, function* () { yield messageThreadProps.onUpdateMessage(messageId, content); }); }, [messageThreadProps]); return React.createElement(Stack, { className: chatContainer, grow: true }, (options === null || options === void 0 ? void 0 : options.topic) !== false && React.createElement(ChatHeader, Object.assign({}, headerProps)), React.createElement(Stack, { className: chatArea, tokens: participantListContainerPadding, horizontal: true, grow: true }, React.createElement(Stack, { className: chatWrapper, grow: true }, (options === null || options === void 0 ? void 0 : options.errorBar) !== false && React.createElement(ErrorBar, Object.assign({}, errorBarProps)), React.createElement(MessageThread, Object.assign({}, messageThreadProps, { onUpdateMessage: onUpdateMessageHandler, onRenderAvatar: onRenderAvatarCallback, onRenderMessage: onRenderMessage, inlineImageOptions: inlineImageOptions, numberOfChatMessagesToReload: defaultNumberOfChatMessagesToReload, styles: messageThreadStyles })), React.createElement(Stack, { className: mergeStyles(sendboxContainerStyles) }, React.createElement("div", { className: mergeStyles(typingIndicatorContainerStyles) }, onRenderTypingIndicator ? onRenderTypingIndicator(typingIndicatorProps.typingUsers) : React.createElement(TypingIndicator, Object.assign({}, typingIndicatorProps, { styles: typingIndicatorStyles }))), React.createElement(Stack, { horizontal: formFactor === 'mobile' }, formFactor === 'mobile', React.createElement(Stack, { grow: true }, React.createElement(SendBoxPicker, { styles: sendBoxStyles, autoFocus: options === null || options === void 0 ? void 0 : options.autoFocus, // we need to overwrite onSendMessage for SendBox because we need to clear attachment state // when submit button is clicked onSendMessage: onSendMessageHandler })), formFactor !== 'mobile')))), overlayImageItem && React.createElement(ImageOverlay, Object.assign({}, overlayImageItem, { isOpen: isImageOverlayOpen, onDismiss: () => { setOverlayImageItem(undefined); setIsImageOverlayOpen(false); adapter.removeResourceFromCache({ threadId, messageId: overlayImageItem.messageId, resourceUrl: overlayImageItem.imageUrl }); }, onDownloadButtonClicked: onDownloadButtonClicked }))); }; //# sourceMappingURL=ChatScreen.js.map