+195
@@ -0,0 +1,195 @@
|
||||
import React from 'react';
|
||||
import { PauseCircle, PlayCircle, Trash2 } from 'react-feather';
|
||||
import { Row, Col, Typography, Divider, Progress, Button, Tooltip } from 'antd';
|
||||
import { capitalize } from 'lodash';
|
||||
|
||||
import {
|
||||
isPendingFileStatusCancel,
|
||||
isPendingFileStatusError,
|
||||
isPendingFileStatusPause,
|
||||
isPendingFileStatusPending,
|
||||
isPendingFileStatusSuccess,
|
||||
} from '../../../features/files/utils/pending-files';
|
||||
import Languages from 'app/features/global/services/languages-service';
|
||||
import { useUpload } from 'app/features/files/hooks/use-upload';
|
||||
import { PendingFileRecoilType, PendingFileType } from 'app/features/files/types/file';
|
||||
|
||||
import '../../file/file.scss';
|
||||
|
||||
type PropsType = {
|
||||
pendingFileState: PendingFileRecoilType;
|
||||
pendingFile: PendingFileType;
|
||||
};
|
||||
|
||||
const { Text } = Typography;
|
||||
export default ({ pendingFileState, pendingFile }: PropsType) => {
|
||||
const { pauseOrResumeUpload, cancelUpload } = useUpload();
|
||||
|
||||
const getProgressStrokeColor = (status: PendingFileRecoilType['status']) => {
|
||||
if (isPendingFileStatusCancel(status)) return 'var(--error)';
|
||||
if (isPendingFileStatusError(status)) return 'var(--error)';
|
||||
if (isPendingFileStatusPause(status)) return 'var(--warning)';
|
||||
if (isPendingFileStatusPending(status)) return 'var(--progress-bar-color)';
|
||||
|
||||
return 'var(--success)';
|
||||
};
|
||||
|
||||
const setStatus = () => {
|
||||
switch (pendingFileState.status) {
|
||||
case 'error':
|
||||
case 'pause':
|
||||
case 'cancel':
|
||||
return 'exception';
|
||||
case 'pending':
|
||||
return 'active';
|
||||
case 'success':
|
||||
return 'success';
|
||||
default:
|
||||
return 'normal';
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
backgroundColor:
|
||||
isPendingFileStatusCancel(pendingFileState.status) ||
|
||||
isPendingFileStatusError(pendingFileState.status)
|
||||
? 'var(--error-background)'
|
||||
: undefined,
|
||||
}}
|
||||
>
|
||||
<Row
|
||||
justify="space-between"
|
||||
align="middle"
|
||||
wrap={false}
|
||||
style={{
|
||||
height: 39,
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<Col className="small-left-margin" flex="auto" style={{ lineHeight: '16px' }}>
|
||||
{pendingFile?.originalFile.name ? (
|
||||
<Row justify="start" align="middle" wrap={false}>
|
||||
<Text
|
||||
ellipsis
|
||||
style={{
|
||||
maxWidth: isPendingFileStatusPause(pendingFile.status) ? 130 : 160,
|
||||
verticalAlign: 'middle',
|
||||
}}
|
||||
>
|
||||
{capitalize(pendingFile?.originalFile.name)}
|
||||
</Text>
|
||||
{isPendingFileStatusPause(pendingFile.status) && (
|
||||
<Text type="secondary" style={{ verticalAlign: 'middle', marginLeft: 4 }}>
|
||||
{/* TODO Add translation here */}
|
||||
(paused)
|
||||
</Text>
|
||||
)}
|
||||
</Row>
|
||||
) : (
|
||||
<div
|
||||
style={{
|
||||
marginTop: 8,
|
||||
height: 8,
|
||||
maxWidth: 160,
|
||||
borderRadius: 8,
|
||||
backgroundColor: 'var(--grey-background)',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Col>
|
||||
|
||||
<Col
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
lineHeight: '16px',
|
||||
}}
|
||||
>
|
||||
{pendingFileState.id ? (
|
||||
!isPendingFileStatusSuccess(pendingFileState.status) &&
|
||||
!isPendingFileStatusError(pendingFileState.status) ? (
|
||||
<Tooltip
|
||||
placement="top"
|
||||
title={
|
||||
isPendingFileStatusPause(pendingFileState.status)
|
||||
? Languages.t('general.resume')
|
||||
: Languages.t('general.pause')
|
||||
}
|
||||
>
|
||||
<Button
|
||||
type="link"
|
||||
shape="circle"
|
||||
disabled={isPendingFileStatusError(pendingFileState.status)}
|
||||
icon={
|
||||
isPendingFileStatusPause(pendingFileState.status) ? (
|
||||
<PlayCircle size={16} color="var(--black)" />
|
||||
) : (
|
||||
<PauseCircle size={16} color="var(--black)" />
|
||||
)
|
||||
}
|
||||
onClick={() => pauseOrResumeUpload(pendingFileState.id)}
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<div style={{ width: 32 }} />
|
||||
)
|
||||
) : (
|
||||
<div
|
||||
style={{
|
||||
marginTop: 8,
|
||||
height: 8,
|
||||
maxWidth: 32,
|
||||
borderRadius: 8,
|
||||
backgroundColor: 'var(--grey-background)',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Col>
|
||||
|
||||
<Col
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
lineHeight: '16px',
|
||||
}}
|
||||
>
|
||||
{!isPendingFileStatusSuccess(pendingFileState.status) &&
|
||||
!isPendingFileStatusError(pendingFileState.status) ? (
|
||||
<Tooltip title={Languages.t('general.cancel')} placement="top">
|
||||
<Button
|
||||
type="link"
|
||||
shape="circle"
|
||||
icon={<Trash2 size={16} color={'var(--black)'} />}
|
||||
onClick={() => cancelUpload(pendingFileState.id)}
|
||||
style={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}
|
||||
/>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<div style={{ width: 32 }} />
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
<div className="file-progress-bar-container">
|
||||
<Progress
|
||||
type="line"
|
||||
className="file-progress-bar"
|
||||
percent={pendingFileState.progress * 100}
|
||||
showInfo={false}
|
||||
trailColor="var(--white)"
|
||||
status={setStatus()}
|
||||
strokeColor={getProgressStrokeColor(pendingFileState.status)}
|
||||
/>
|
||||
</div>
|
||||
<Divider style={{ margin: 0 }} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { Minus, Plus } from 'react-feather';
|
||||
import { Layout, Row, Col, Typography } from 'antd';
|
||||
import PerfectScrollbar from 'react-perfect-scrollbar';
|
||||
import moment from 'moment';
|
||||
|
||||
import PendingFileRow from './pending-file-row';
|
||||
import Languages from 'app/features/global/services/languages-service';
|
||||
import { PendingFileRecoilType } from 'app/features/files/types/file';
|
||||
import { useUpload } from 'app/features/files/hooks/use-upload';
|
||||
|
||||
import './styles.scss';
|
||||
|
||||
type PropsType = {
|
||||
pendingFilesState: PendingFileRecoilType[];
|
||||
visible: boolean;
|
||||
};
|
||||
|
||||
const { Text } = Typography;
|
||||
const { Header, Content } = Layout;
|
||||
export default ({ pendingFilesState, visible }: PropsType) => {
|
||||
const { getOnePendingFile, currentTask } = useUpload();
|
||||
const [hiddenPendingFiles, setHiddenPendingFiles] = useState<boolean>(false);
|
||||
|
||||
const handleTimeChange = useCallback(() => {
|
||||
const pendingFiles = pendingFilesState.map(state => getOnePendingFile(state.id));
|
||||
const uploadingFiles = pendingFiles.filter(f => f?.resumable && f.resumable.isUploading());
|
||||
|
||||
const remainingSizeTotal = uploadingFiles
|
||||
.map(f => (1 - f.progress) * (f?.originalFile?.size || 0))
|
||||
.reduce((accumulator: number, nextValue: number) => accumulator + nextValue, 0);
|
||||
|
||||
const speed =
|
||||
uploadingFiles
|
||||
.map(f => f.speed)
|
||||
.reduce((accumulator: number, nextValue: number) => accumulator + nextValue, 0) /
|
||||
uploadingFiles.map(f => f.speed).length;
|
||||
|
||||
const timeRemainingInMs = remainingSizeTotal / speed;
|
||||
|
||||
const momentTimeRemaining = moment(new Date().getTime() + timeRemainingInMs).fromNow();
|
||||
|
||||
// TODO translation
|
||||
if (momentTimeRemaining !== 'Invalid date') {
|
||||
return `Will end ${momentTimeRemaining}...`;
|
||||
} else {
|
||||
return `Waiting for time approximations...`;
|
||||
}
|
||||
}, [getOnePendingFile, pendingFilesState]);
|
||||
|
||||
return pendingFilesState.length > 0 ? (
|
||||
<Layout className={'pending-files-list-layout ' + (visible ? 'visible' : '')}>
|
||||
<Header
|
||||
className={classNames('pending-files-list-header', {
|
||||
hidden: hiddenPendingFiles,
|
||||
})}
|
||||
onClick={() => setHiddenPendingFiles(!hiddenPendingFiles)}
|
||||
>
|
||||
<Row justify="space-between" align="middle">
|
||||
<Col>
|
||||
<Text style={{ color: 'var(--white)' }}>
|
||||
{currentTask.total > 0 && `${currentTask.uploaded}/${currentTask.total} `}
|
||||
{Languages.t('components.drive_dropzone.uploading')}
|
||||
</Text>
|
||||
</Col>
|
||||
<Col style={{ display: 'flex', alignItems: 'center' }}>
|
||||
{hiddenPendingFiles ? <Plus size={18} /> : <Minus size={18} />}
|
||||
</Col>
|
||||
</Row>
|
||||
</Header>
|
||||
{!hiddenPendingFiles && (
|
||||
<Content className="pending-files-list-content">
|
||||
<PerfectScrollbar
|
||||
options={{ suppressScrollX: true, suppressScrollY: false }}
|
||||
component="div"
|
||||
style={{ width: '100%', height: 114 }}
|
||||
>
|
||||
<Row justify="start" align="middle" style={{ background: '#DFE7FE' }}>
|
||||
<Col className="small-left-margin">
|
||||
<Text style={{ color: '#6C6C6D' }}>{handleTimeChange()}</Text>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<>
|
||||
{pendingFilesState.length > 0 &&
|
||||
pendingFilesState.map((pendingFileState, index) => (
|
||||
<PendingFileRow
|
||||
key={`${pendingFileState.file?.id}-${index}`}
|
||||
pendingFileState={pendingFileState}
|
||||
pendingFile={getOnePendingFile(pendingFileState.id)}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
</PerfectScrollbar>
|
||||
</Content>
|
||||
)}
|
||||
</Layout>
|
||||
) : (
|
||||
<></>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,41 @@
|
||||
.pending-files-list-layout {
|
||||
border: 1px solid var(--black-alpha-70);
|
||||
box-shadow: var(--box-shadow-base);
|
||||
width: 256px;
|
||||
|
||||
border-radius: var(--border-radius-base);
|
||||
position: absolute;
|
||||
z-index: 100;
|
||||
bottom: 8px;
|
||||
right: 24px;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.2s;
|
||||
transition-delay: 1s;
|
||||
|
||||
&.visible {
|
||||
opacity: 1;
|
||||
transition-delay: 0s;
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
.pending-files-list-header {
|
||||
color: var(--white);
|
||||
padding: 0 8px;
|
||||
background-color: var(--secondary);
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
border-radius: 8px 8px 0 0;
|
||||
cursor: pointer;
|
||||
|
||||
&.hidden {
|
||||
border-radius: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.pending-files-list-content {
|
||||
min-height: 32px;
|
||||
max-height: 176px;
|
||||
overflow-y: none;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import React from 'react';
|
||||
import { useUpload } from 'app/features/files/hooks/use-upload';
|
||||
import PendingFilesList from './pending-file-components/pending-files-list';
|
||||
|
||||
const ChatUploadsViewer = (): JSX.Element => {
|
||||
const { currentTask } = useUpload();
|
||||
|
||||
return (
|
||||
<PendingFilesList
|
||||
visible={!!currentTask && currentTask.files.length > 0 && !currentTask.completed}
|
||||
pendingFilesState={currentTask.files}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChatUploadsViewer;
|
||||
Reference in New Issue
Block a user