📁 Changed TDrive root folder (#16)

📁 Changed TDrive root folder
This commit is contained in:
Montassar Ghanmy
2023-04-11 10:04:29 +01:00
committed by GitHub
parent 3b3f67af07
commit e0615fa867
10548 changed files with 48 additions and 48 deletions
@@ -0,0 +1,166 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import React, { useState, useEffect } from 'react';
import classNames, { Argument } from 'classnames';
import { FileThumbnail, FileDetails, FileActions, FileProgress } from './parts';
import {
isPendingFileStatusCancel,
isPendingFileStatusError,
isPendingFileStatusSuccess,
} from 'app/features/files/utils/pending-files';
import { DataFileType } from './types';
import DriveService from 'app/deprecated/Apps/Drive/Drive.js';
import RouterService from 'app/features/router/services/router-service';
import './file.scss';
import { PendingFileRecoilType } from 'app/features/files/types/file';
import Api from 'app/features/global/framework/api-service';
import FileUploadAPIClient from '../../features/files/api/file-upload-api-client';
import LargePreview from './parts/large-preview';
import { MessageFileType } from 'app/features/messages/types/message';
import { useFileViewerModal } from 'app/features/viewer/hooks/use-viewer';
type PropsType = {
source: 'internal' | 'drive' | string;
externalId: string | any;
file: DataFileType;
messageFile: MessageFileType;
context: 'input' | 'message' | 'drive';
progress?: number;
status?: PendingFileRecoilType['status'];
onRemove?: () => void;
className?: string;
large?: boolean;
xlarge?: boolean;
};
export default ({
source,
externalId,
file: _file,
messageFile,
className,
context,
progress,
status,
onRemove,
large,
xlarge,
}: PropsType) => {
const { companyId, workspaceId } = RouterService.getStateFromRoute();
const [file, setFile] = useState<DataFileType>(_file);
const classNameArguments: Argument[] = [
'file-component',
className,
{ 'large-view': large },
{
'file-component-error':
status && (isPendingFileStatusError(status) || isPendingFileStatusCancel(status)),
'file-component-uploading': progress != undefined && progress < 1,
},
];
const { open: openViewer } = useFileViewerModal();
useEffect(() => {
if (source === 'drive') {
(async () => {
if (typeof externalId === 'string') {
externalId = { id: externalId, workspace_id: workspaceId };
}
let driveFile = (await Api.post('/ajax/drive/v2/find', {
options: {
element_id: externalId?.id,
workspace_id: externalId?.workspace_id,
},
})) as any;
driveFile = driveFile?.data || {};
setFile({
...file,
thumbnail: driveFile.preview_link,
name: driveFile.name,
size: driveFile.size,
type: FileUploadAPIClient.mimeToType(
FileUploadAPIClient.extensionToMime(driveFile.extension),
),
});
})();
} else {
setFile(_file);
}
}, [_file]);
const onClickFile = async () => {
if (source === 'internal') {
//Only if upload has ended
if ((!status || isPendingFileStatusSuccess(status)) && file.id) openViewer(messageFile);
}
if (source === 'drive') {
if (typeof externalId === 'string') {
externalId = { id: externalId, workspace_id: workspaceId };
}
const file = (await Api.post('/ajax/drive/v2/find', {
options: {
element_id: externalId?.id,
workspace_id: externalId?.workspace_id,
},
})) as any;
DriveService.viewDocument(file?.data, context === 'input');
}
};
let computedHeight = 200;
let computedWidth = file.thumbnail_ratio * 200;
const isMediaFile = ['image', 'video'].includes(file.type);
if (xlarge) {
computedWidth = Math.max(
160,
Math.min(
Math.min(messageFile.metadata?.thumbnails?.[0]?.width || 600, 600),
file.thumbnail_ratio * document.body.clientHeight * 0.5,
),
);
computedHeight = Math.max(
200,
Math.min(
Math.min(
messageFile.metadata?.thumbnails?.[0]?.height || 10000,
document.body.clientHeight * 0.5,
),
computedWidth / file.thumbnail_ratio,
),
);
}
return (
<div
className={classNames(classNameArguments)}
style={large ? { width: computedWidth, height: computedHeight } : {}}
onClick={() => companyId && onClickFile()}
>
{large && <LargePreview file={file} />}
<div
className={classNames('file-info-container', {
'media-file-info-container': isMediaFile,
})}
>
<FileThumbnail file={file} />
<FileDetails file={file} source={source} />
<FileActions
deletable={context === 'input'}
actionMenu={context === 'message' && source === 'internal'}
status={status}
file={file}
messageFile={messageFile}
onRemove={onRemove}
source={source}
/>
</div>
<FileProgress progress={progress} status={status} file={file} />
</div>
);
};
@@ -0,0 +1,160 @@
.file-component {
border: 1px solid var(--grey-light);
box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.1);
border-radius: 8px 8px 8px 8px;
width: 218px;
display: flex;
flex-direction: column;
justify-content: space-between;
overflow: hidden;
min-height: 50px;
&:hover {
cursor: pointer;
}
&.large-view {
min-height: 200px;
max-height: 500px;
max-width: 90%;
min-width: 160px;
position: relative;
.file-large-preview {
position: absolute;
height: 100%;
top: 0px;
left: 0px;
right: 0px;
background-size: contain;
background-position: center;
background-repeat: no-repeat;
}
&:hover {
.file-info-container {
opacity: 1;
transition: opacity 0s;
}
}
.file-info-container {
background-color: #fffffff0;
position: absolute;
border-top: 1px solid var(--grey-light);
bottom: 0px;
width: 100%;
.file-thumbnail-container {
display: none;
}
}
.media-file-info-container {
opacity: 0;
transition: opacity 0.2s;
}
.file-large-preview-play-container {
width: 64px;
height: 64px;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
border-radius: 15px;
}
}
&.file-component-uploading {
border-radius: 8px 8px 0px 0px;
& .file-info-container {
opacity: 0.5;
}
.file-info-container {
padding-bottom: 0;
}
.file-progress-bar-container {
padding-top: 3px;
padding-bottom: 3px;
height: 5px;
}
}
&.file-component-error {
background: var(--error-background);
}
.file-info-container {
display: flex;
flex-direction: row;
justify-content: space-between;
padding: 8px 8px 6px 8px;
.file-thumbnail-container {
display: flex;
align-items: center;
justify-content: center;
width: 32px;
.file-thumbnail-component {
border-radius: 4px;
}
}
.file-component-details {
overflow: hidden;
flex: 1;
display: flex;
flex-direction: column;
.ant-tag {
height: 15px;
line-height: 15px;
padding: 0 4px;
}
}
}
.file-menu {
display: flex;
align-items: center;
justify-content: space-between;
.ant-btn {
display: flex;
justify-content: center;
align-items: center;
&:hover {
color: var(--black);
}
color: var(--grey-dark);
background-color: transparent;
}
}
}
.file-progress-bar-container {
display: flex;
.file-progress-bar {
.ant-progress-outer {
height: 3px;
display: flex;
.ant-progress-inner {
border-radius: 0px;
.ant-progress-bg {
border-radius: 2px;
height: 3px !important;
transition: all 0.2s;
}
}
}
}
}
@@ -0,0 +1,152 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import React, { useRef } from 'react';
import { Button } from 'antd';
import { MoreHorizontal, RotateCw, X } from 'react-feather';
import {
isPendingFileStatusPending,
isPendingFileStatusPause,
isPendingFileStatusSuccess,
isPendingFileStatusError,
isPendingFileStatusCancel,
} from 'app/features/files/utils/pending-files';
import Languages from 'app/features/global/services/languages-service';
import { useUpload } from 'app/features/files/hooks/use-upload';
import { DataFileType } from '../types';
import MenuManager from 'app/components/menus/menus-manager';
import { PendingFileRecoilType } from 'app/features/files/types/file';
import { MessageFileType } from 'app/features/messages/types/message';
import { useFileViewerModal } from 'app/features/viewer/hooks/use-viewer';
import { useEditors } from 'app/views/client/viewer/other/editors-service';
type PropsType = {
file: DataFileType;
messageFile: MessageFileType;
status?: PendingFileRecoilType['status'];
deletable?: boolean;
actionMenu?: boolean;
onRemove?: () => void;
source?: string;
};
export const FileActions = ({
file,
messageFile,
status,
deletable,
actionMenu,
onRemove,
}: PropsType): JSX.Element => {
const { cancelUpload, deleteOneFile, downloadOneFile, retryUpload } = useUpload();
const menuRef = useRef<HTMLElement>();
const { open: openPreview } = useFileViewerModal();
const { candidates } = useEditors(file.name.split('.').pop() || '');
const onClickDownload = async () => {
file.company_id &&
(await downloadOneFile({
companyId: file.company_id,
fileId: file.id,
messageFile,
}));
};
const onClickOpen = async () => {
openPreview(messageFile);
};
const buildMenu = (e: React.MouseEvent<HTMLElement, MouseEvent>) => {
e.stopPropagation();
const menu = [
{
type: 'menu',
text: Languages.t('scenes.apps.drive.download_button'),
onClick: onClickDownload,
},
];
if (candidates.length > 0) {
const openerName = candidates[0].name || candidates[0].app?.identity.name;
menu.push({
type: 'menu',
text: Languages.t(
'scenes.apps.drive.viewer.edit_with_button',
[openerName],
`Edit with ${openerName}`,
),
onClick: onClickOpen,
});
}
MenuManager.openMenu(menu, (window as any).getBoundingClientRect(menuRef.current), null, {
margin: 0,
});
};
const onClickCancel = (e: React.MouseEvent<HTMLElement, MouseEvent>) => {
e.preventDefault();
e.stopPropagation();
if (status && isPendingFileStatusSuccess(status)) {
if (file.id) deleteOneFile(file.id);
} else {
cancelUpload(file.id);
}
if (onRemove) onRemove();
};
const onClickRetry = () => {
retryUpload(file.id);
};
const setActions = () => {
if (actionMenu) {
return (
<Button
ref={node => node && (menuRef.current = node)}
shape="circle"
icon={<MoreHorizontal size={16} />}
onClick={buildMenu}
/>
);
}
if (deletable && status) {
if (isPendingFileStatusError(status)) {
return (
<>
<Button
shape="circle"
icon={<RotateCw size={16} color="var(--error)" />}
onClick={onClickRetry}
/>
<Button
shape="circle"
icon={<X size={16} color="var(--error)" />}
onClick={onClickCancel}
/>
</>
);
}
if (
isPendingFileStatusPending(status) ||
isPendingFileStatusPause(status) ||
isPendingFileStatusSuccess(status)
) {
return <Button shape="circle" icon={<X size={16} />} onClick={onClickCancel} />;
}
if (isPendingFileStatusCancel(status)) {
return <></>;
}
}
};
return <div className="file-menu">{setActions()}</div>;
};
export default FileActions;
@@ -0,0 +1,37 @@
import React from 'react';
import { Tag, Typography } from 'antd';
import { capitalize } from 'lodash';
import Numbers from 'app/features/global/utils/Numbers';
import { DataFileType } from '../types';
type PropsType = {
file: DataFileType;
source: string;
};
const setRealFileSize = (file: DataFileType): string => Numbers.humanFileSize(file.size, true);
const setFileType = (file: DataFileType): string => capitalize(file.type.split('/')[0]);
const { Text } = Typography;
export const FileDetails = ({ file, source }: PropsType) => {
let sourceTag: string | null = null;
if (source === 'drive') {
sourceTag = 'Drive';
}
return (
<div className="file-component-details">
<Text ellipsis style={{ verticalAlign: 'middle' }}>
{file.name}
</Text>
<Text type="secondary" ellipsis style={{ verticalAlign: 'middle' }}>
{!!sourceTag && <Tag color={'orange'}>{sourceTag}</Tag>}
{setRealFileSize(file)} {setFileType(file)}
</Text>
</div>
);
};
export default FileDetails;
@@ -0,0 +1,4 @@
export * from './details';
export * from './thumbnail';
export * from './actions';
export * from './progress';
@@ -0,0 +1,33 @@
import React from 'react';
import { DataFileType } from '../types';
import { Play } from 'react-feather';
type PropsType = {
file: DataFileType;
};
const LargePreview = ({ file: { thumbnail, type } }: PropsType): JSX.Element => {
return (
<div
className="file-large-preview"
style={{
backgroundImage: `url(${thumbnail})`,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
}}
>
{type === 'video' && (
<div className='file-large-preview-play-container'>
<Play
size={32}
color={'white'}
strokeWidth={3}
/>
</div>
)}
</div>
);
};
export default LargePreview;
@@ -0,0 +1,61 @@
import React from 'react';
import { Progress } from 'antd';
import { DataFileType } from '../types';
import { PendingFileRecoilType } from 'app/features/files/types/file';
import {
isPendingFileStatusPending,
isPendingFileStatusPause,
isPendingFileStatusSuccess,
isPendingFileStatusError,
isPendingFileStatusCancel,
} from 'app/features/files/utils/pending-files';
type PropsType = {
file: DataFileType;
status?: PendingFileRecoilType['status'];
progress?: number;
};
const setStatus = (status: PendingFileRecoilType['status']): 'normal' | 'exception' | 'active' => {
switch (status) {
case 'error':
case 'pause':
return 'exception';
case 'pending':
return 'active';
default:
return 'normal';
}
};
export const FileProgress = ({ status, progress }: PropsType): JSX.Element => {
const setProgressStrokeColor = (): string => {
if (!status) return '';
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)';
};
return status && !isPendingFileStatusSuccess(status) && progress != undefined ? (
<div className="file-progress-bar-container">
<Progress
type="line"
className="file-progress-bar"
percent={progress * 100}
showInfo={false}
status={setStatus(status)}
strokeColor={setProgressStrokeColor()}
trailColor="var(--progress-bar-background)"
/>
</div>
) : (
<div className="file-progress-bar-container" />
);
};
export default FileProgress;
@@ -0,0 +1,42 @@
import React from 'react';
import classNames from 'classnames';
import { FileText, Film, Headphones, Archive, Link, Image } from 'react-feather';
import { DataFileType } from '../types';
type PropsType = {
file: DataFileType;
};
export const FileThumbnail = ({ file }: PropsType): JSX.Element => {
const type = file.type;
const canHavePreview = ['image', 'video', 'pdf', 'document', 'slides', 'spreadsheet'].includes(
type,
);
return (
<div className={classNames('file-thumbnail-container', 'small-right-margin')}>
{canHavePreview && file.thumbnail && (
<div
className="ant-image file-thumbnail-component"
style={{
width: 32,
height: 32,
backgroundImage: `url(${file.thumbnail})`,
backgroundSize: 'cover',
backgroundPosition: 'center',
}}
/>
)}
{type === 'image' && !file.thumbnail && <Image size={20} />}
{type === 'video' && !file.thumbnail && <Film size={20} />}
{['pdf', 'document', 'slides', 'spreadsheet', 'other'].includes(type) && !file.thumbnail && (
<FileText size={20} />
)}
{type === 'audio' && <Headphones size={20} />}
{type === 'archive' && <Archive size={20} />}
{type === 'link' && <Link size={20} />}
</div>
);
};
export default FileThumbnail;
@@ -0,0 +1,9 @@
export type DataFileType = {
id: string;
name: string;
thumbnail?: string;
thumbnail_ratio: number;
company_id?: string;
size: number;
type: string;
};