import React, { useCallback, useState, useRef } from 'react'; import { useTranslation } from 'react-i18next'; import { useDispatch, useSelector, useStore } from 'react-redux'; import { makeStyles } from 'tss-react/mui'; import { IReduxState } from '../../../app/types'; import Avatar from '../../../base/avatar/components/Avatar'; import { IconCloudUpload, IconDownload, IconTrash } from '../../../base/icons/svg'; import Button from '../../../base/ui/components/web/Button'; import { BUTTON_TYPES } from '../../../base/ui/constants.web'; import Icon from '../../../base/icons/components/Icon'; import { isLocalParticipantModerator } from '../../../base/participants/functions'; import { withPixelLineHeight } from '../../../base/styles/functions.web'; import BaseTheme from '../../../base/ui/components/BaseTheme.web'; import { downloadFile, removeFile } from '../../actions'; import { formatFileSize, formatTimestamp, getFileIcon, processFiles } from '../../functions.any'; const useStyles = makeStyles()(theme => { return { buttonContainer: { alignItems: 'center', display: 'flex', justifyContent: 'end', gap: theme.spacing(2), position: 'absolute', top: 0, right: theme.spacing(3), bottom: 0, left: 0 }, container: { boxSizing: 'border-box', display: 'flex', flexDirection: 'column', height: '100%', margin: '0 auto', maxWidth: '600px', padding: theme.spacing(3), position: 'relative', width: '100%' }, dropZone: { backgroundColor: theme.palette.ui02, border: `2px dashed ${theme.palette.ui03}`, borderRadius: theme.shape.borderRadius, bottom: 0, left: 0, opacity: 0, position: 'absolute', right: 0, top: 0, zIndex: 0, '&.dragging': { backgroundColor: theme.palette.ui03, borderColor: theme.palette.action01, opacity: 0.8, zIndex: 2 } }, fileIconContainer: { display: 'flex', margin: 'auto' }, fileItem: { backgroundColor: theme.palette.ui02, borderRadius: theme.shape.borderRadius, display: 'flex', flexDirection: 'row', gap: theme.spacing(3), justifyContent: 'space-between', padding: theme.spacing(3), position: 'relative', '&:hover': { backgroundColor: theme.palette.ui03, borderRadius: theme.shape.borderRadius, '& .actionIconVisibility': { visibility: 'visible' }, '& .timestampVisibility': { visibility: 'hidden' } } }, fileItemDetails: { display: 'flex', flexDirection: 'column', flexGrow: 2, gap: theme.spacing(1), justifyContent: 'center', minWidth: 0 }, fileList: { display: 'flex', flex: 1, flexDirection: 'column', gap: theme.spacing(2), gridTemplateColumns: 'repeat(auto-fill, minmax(200px, 1fr))', marginBottom: theme.spacing(8), overflowY: 'auto', zIndex: 1 }, fileName: { ...theme.typography.labelBold, gap: theme.spacing(1), overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }, fileAuthorParticipant: { alignItems: 'center', display: 'inline-flex', gap: theme.spacing(1) }, fileAuthorParticipantName: { ...theme.typography.labelBold, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }, fileSize: { ...theme.typography.labelRegular }, fileTimestamp: { ...theme.typography.labelRegular, display: 'flex', lineHeight: '1.2rem', marginTop: theme.spacing(1), textAlign: 'center', }, hiddenInput: { visibility: 'hidden' }, noFilesContainer: { display: 'flex', flexDirection: 'column', height: '88%', justifyContent: 'center', textAlign: 'center' }, noFilesText: { ...withPixelLineHeight(theme.typography.bodyLongBold), color: theme.palette.text02, padding: '0 24px', textAlign: 'center' }, progressBar: { backgroundColor: theme.palette.ui03, borderRadius: theme.shape.borderRadius, height: 4, overflow: 'hidden', width: '100%' }, progressFill: { backgroundColor: theme.palette.action01, height: '100%', transition: 'width 0.3s ease' }, uploadButton: { bottom: theme.spacing(4), cursor: 'pointer', left: '50%', position: 'absolute', transform: 'translateX(-50%)', width: '85%', zIndex: 1 }, uploadIcon: { margin: '0 auto' }, actionIcon: { cursor: 'pointer', padding: theme.spacing(1), visibility: 'hidden' } }; }); const FileSharing = () => { const { classes } = useStyles(); const [ isDragging, setIsDragging ] = useState(false); const fileInputRef = useRef(null); const { t } = useTranslation(); const dispatch = useDispatch(); const store = useStore(); const { files } = useSelector((state: IReduxState) => state['features/file-sharing']); const sortedFiles = Array.from(files.values()).sort((a, b) => a.fileName.localeCompare(b.fileName)); const isModerator = useSelector(isLocalParticipantModerator); const handleDragEnter = useCallback((e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); setIsDragging(true); }, []); const handleDragLeave = useCallback((e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); setIsDragging(false); }, []); const handleDragOver = useCallback((e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); }, []); const handleFileSelect = useCallback((e: React.ChangeEvent) => { if (e.target.files) { processFiles(e.target.files as FileList, store); } }, [ processFiles ]); const handleDrop = useCallback((e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); setIsDragging(false); if (e.dataTransfer.files?.length > 0) { processFiles(e.dataTransfer.files as FileList, store); } }, [ processFiles ]); const handleClick = useCallback(() => { fileInputRef.current?.click(); }, []); const handleKeyPress = useCallback((e: React.KeyboardEvent) => { if (e.key === 'Enter' || e.key === ' ') { fileInputRef.current?.click(); } }, []); return (
{ isModerator && ( <>
{ sortedFiles.length === 0 && (
{ t('fileSharing.dragAndDrop') }
) } ) } { sortedFiles.length > 0 && (
{ sortedFiles.map(file => (
{ (file.progress ?? 100) === 100 && ( <>
{ file.fileName }
{ formatFileSize(file.fileSize) }
{ file.authorParticipantName }
                                                        { formatTimestamp(file.timestamp) }
                                                    
dispatch(downloadFile(file.fileId)) } size = { 24 } src = { IconDownload } /> { isModerator && ( dispatch(removeFile(file.fileId)) } size = { 24 } src = { IconTrash } /> ) }
) } { (file.progress ?? 100) < 100 && (
) }
)) }
) } { isModerator && (
); }; export default FileSharing;