// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. import React, {useCallback, useEffect, useRef} from 'react'; import {useIntl} from 'react-intl'; import {addFilesToDraft, removeDraft} from '@actions/local/draft'; import {useServerUrl} from '@context/server'; import useFileUploadError from '@hooks/file_upload_error'; import {useFetchRenderPermissions} from '@hooks/render_permissions'; import DraftEditPostUploadManager from '@managers/draft_upload_manager'; import {fileMaxWarning, fileSizeWarning, getUploadErrorMessage, uploadDisabledByPolicyWarning, uploadDisabledWarning} from '@utils/file'; import SendHandler from '../send_handler'; import type {ErrorHandlers} from '@typings/components/upload_error_handlers'; import type {AvailableScreens} from '@typings/screens/navigation'; type Props = { testID?: string; channelId: string; cursorPosition: number; rootId?: string; canShowPostPriority?: boolean; files?: FileInfo[]; maxFileCount: number; maxFileSize: number; canUploadFiles: boolean; canUploadFilesByPolicy: boolean; updateCursorPosition: React.Dispatch>; updatePostInputTop: (top: number) => void; updateValue: React.Dispatch>; value: string; setIsFocused: (isFocused: boolean) => void; onPostCreated?: (postId: string) => void; location?: AvailableScreens; } const emptyFileList: FileInfo[] = []; export default function DraftHandler(props: Props) { const { testID, channelId, cursorPosition, rootId = '', canShowPostPriority, files, maxFileCount, maxFileSize, canUploadFiles, canUploadFilesByPolicy, updateCursorPosition, updatePostInputTop, updateValue, value, setIsFocused, onPostCreated, location, } = props; const serverUrl = useServerUrl(); const intl = useIntl(); useFetchRenderPermissions(channelId); const uploadErrorHandlers = useRef({}); const {uploadError, newUploadError} = useFileUploadError(); const handleUploadError = useCallback((errorMessage: string, errorName?: string) => { newUploadError(getUploadErrorMessage(intl, errorMessage, errorName)); }, [intl, newUploadError]); const clearDraft = useCallback(() => { removeDraft(serverUrl, channelId, rootId); updateValue(''); }, [serverUrl, channelId, rootId, updateValue]); const addFiles = useCallback((newFiles: FileInfo[]) => { if (!newFiles.length) { return; } if (!canUploadFiles) { newUploadError(uploadDisabledWarning(intl)); return; } if (!canUploadFilesByPolicy) { newUploadError(uploadDisabledByPolicyWarning(intl)); return; } const currentFileCount = files?.length || 0; const availableCount = maxFileCount - currentFileCount; if (newFiles.length > availableCount) { newUploadError(fileMaxWarning(intl, maxFileCount)); return; } const largeFile = newFiles.find((file) => file.size > maxFileSize); if (largeFile) { newUploadError(fileSizeWarning(intl, maxFileSize)); return; } addFilesToDraft(serverUrl, channelId, rootId, newFiles); for (const file of newFiles) { DraftEditPostUploadManager.prepareUpload(serverUrl, file, channelId, rootId); uploadErrorHandlers.current[file.clientId!] = DraftEditPostUploadManager.registerErrorHandler(file.clientId!, handleUploadError); } newUploadError(null); }, [canUploadFiles, canUploadFilesByPolicy, files?.length, maxFileCount, serverUrl, channelId, rootId, newUploadError, intl, maxFileSize, handleUploadError]); // This effect mainly handles keeping clean the uploadErrorHandlers, and // reinstantiate them on component mount and file retry. useEffect(() => { let loadingFiles: FileInfo[] = []; if (files) { loadingFiles = files.filter((v) => v.clientId && DraftEditPostUploadManager.isUploading(v.clientId)); } for (const key of Object.keys(uploadErrorHandlers.current)) { if (!loadingFiles.find((v) => v.clientId === key)) { uploadErrorHandlers.current[key]?.(); delete (uploadErrorHandlers.current[key]); } } for (const file of loadingFiles) { if (!uploadErrorHandlers.current[file.clientId!]) { uploadErrorHandlers.current[file.clientId!] = DraftEditPostUploadManager.registerErrorHandler(file.clientId!, handleUploadError); } } }, [files, newUploadError, handleUploadError]); return ( ); }