You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
294 lines
10 KiB
TypeScript
294 lines
10 KiB
TypeScript
// Copyright (C) 2020-2022 Intel Corporation
|
|
// Copyright (C) 2022 CVAT.ai Corporation
|
|
//
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
import './styles.scss';
|
|
import React, { ReactText, RefObject } from 'react';
|
|
|
|
import Tabs from 'antd/lib/tabs';
|
|
import Input from 'antd/lib/input';
|
|
import Text from 'antd/lib/typography/Text';
|
|
import Paragraph from 'antd/lib/typography/Paragraph';
|
|
import { RcFile } from 'antd/lib/upload';
|
|
import Empty from 'antd/lib/empty';
|
|
import Tree, { TreeNodeNormal } from 'antd/lib/tree/Tree';
|
|
import { FormInstance } from 'antd/lib/form';
|
|
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
import { EventDataNode } from 'rc-tree/lib/interface';
|
|
|
|
import consts from 'consts';
|
|
import { CloudStorage } from 'reducers';
|
|
import CloudStorageTab from './cloud-storages-tab';
|
|
import LocalFiles from './local-files';
|
|
|
|
export interface Files {
|
|
local: File[];
|
|
share: string[];
|
|
remote: string[];
|
|
cloudStorage: string[];
|
|
}
|
|
|
|
interface State {
|
|
files: Files;
|
|
expandedKeys: string[];
|
|
active: 'local' | 'share' | 'remote' | 'cloudStorage';
|
|
cloudStorage: CloudStorage | null;
|
|
potentialCloudStorage: string;
|
|
}
|
|
|
|
interface Props {
|
|
treeData: (TreeNodeNormal & { mime_type: string })[];
|
|
share: any;
|
|
many: boolean;
|
|
onLoadData: (key: string) => Promise<any>;
|
|
onChangeActiveKey(key: string): void;
|
|
onUploadLocalFiles(files: File[]): void;
|
|
onUploadRemoteFiles(urls: string[]): void;
|
|
onUploadShareFiles(keys: string[]): Promise<void>;
|
|
onUploadCloudStorageFiles(cloudStorageFiles: string[]): void;
|
|
}
|
|
|
|
export class FileManager extends React.PureComponent<Props, State> {
|
|
private cloudStorageTabFormRef: RefObject<FormInstance>;
|
|
|
|
public constructor(props: Props) {
|
|
super(props);
|
|
this.cloudStorageTabFormRef = React.createRef<FormInstance>();
|
|
const { onLoadData } = this.props;
|
|
|
|
this.state = {
|
|
files: {
|
|
local: [],
|
|
share: [],
|
|
remote: [],
|
|
cloudStorage: [],
|
|
},
|
|
cloudStorage: null,
|
|
potentialCloudStorage: '',
|
|
expandedKeys: [],
|
|
active: 'local',
|
|
};
|
|
|
|
onLoadData('/');
|
|
}
|
|
|
|
private handleUploadCloudStorageFiles = (cloudStorageFiles: string[]): void => {
|
|
const { files } = this.state;
|
|
const { onUploadCloudStorageFiles } = this.props;
|
|
this.setState({
|
|
files: {
|
|
...files,
|
|
cloudStorage: cloudStorageFiles,
|
|
},
|
|
});
|
|
onUploadCloudStorageFiles(cloudStorageFiles);
|
|
};
|
|
|
|
public getCloudStorageId(): number | null {
|
|
const { cloudStorage } = this.state;
|
|
return cloudStorage?.id || null;
|
|
}
|
|
|
|
public getFiles(): Files {
|
|
const { active, files } = this.state;
|
|
return {
|
|
local: active === 'local' ? files.local : [],
|
|
share: active === 'share' ? files.share : [],
|
|
remote: active === 'remote' ? files.remote : [],
|
|
cloudStorage: active === 'cloudStorage' ? files.cloudStorage : [],
|
|
};
|
|
}
|
|
|
|
public reset(): void {
|
|
const { active } = this.state;
|
|
if (active === 'cloudStorage') {
|
|
this.cloudStorageTabFormRef.current?.resetFields();
|
|
}
|
|
this.setState({
|
|
expandedKeys: [],
|
|
active: 'local',
|
|
files: {
|
|
local: [],
|
|
share: [],
|
|
remote: [],
|
|
cloudStorage: [],
|
|
},
|
|
cloudStorage: null,
|
|
potentialCloudStorage: '',
|
|
});
|
|
}
|
|
|
|
private renderLocalSelector(): JSX.Element {
|
|
const { many, onUploadLocalFiles } = this.props;
|
|
const { files } = this.state;
|
|
|
|
return (
|
|
<Tabs.TabPane className='cvat-file-manager-local-tab' key='local' tab='My computer'>
|
|
<LocalFiles
|
|
files={files.local}
|
|
many={many}
|
|
onUpload={(_: RcFile, newLocalFiles: RcFile[]): boolean => {
|
|
this.setState({
|
|
files: {
|
|
...files,
|
|
local: newLocalFiles,
|
|
},
|
|
});
|
|
onUploadLocalFiles(newLocalFiles);
|
|
return false;
|
|
}}
|
|
/>
|
|
</Tabs.TabPane>
|
|
);
|
|
}
|
|
|
|
private renderShareSelector(): JSX.Element {
|
|
function getTreeNodes(data: TreeNodeNormal[]): TreeNodeNormal[] {
|
|
// sort alphabetically
|
|
return data
|
|
.sort((a: TreeNodeNormal, b: TreeNodeNormal): number => (
|
|
a.key.toLocaleString().localeCompare(b.key.toLocaleString())))
|
|
.map((it) => ({
|
|
...it,
|
|
children: it.children ? getTreeNodes(it.children) : undefined,
|
|
}));
|
|
}
|
|
|
|
const { SHARE_MOUNT_GUIDE_URL } = consts;
|
|
const { treeData, onUploadShareFiles, onLoadData } = this.props;
|
|
const { expandedKeys, files } = this.state;
|
|
|
|
return (
|
|
<Tabs.TabPane key='share' tab='Connected file share'>
|
|
{treeData[0].children && treeData[0].children.length ? (
|
|
<Tree
|
|
className='cvat-share-tree'
|
|
checkable
|
|
showLine
|
|
height={256}
|
|
checkStrictly={false}
|
|
expandedKeys={expandedKeys}
|
|
checkedKeys={files.share}
|
|
loadData={(event: EventDataNode): Promise<void> => onLoadData(event.key.toLocaleString())}
|
|
onExpand={(newExpandedKeys: ReactText[]): void => {
|
|
this.setState({
|
|
expandedKeys: newExpandedKeys.map((text: ReactText): string => text.toLocaleString()),
|
|
});
|
|
}}
|
|
onCheck={(
|
|
checkedKeys:
|
|
| ReactText[]
|
|
| {
|
|
checked: ReactText[];
|
|
halfChecked: ReactText[];
|
|
},
|
|
): void => {
|
|
const keys = (checkedKeys as ReactText[]).map((text: ReactText): string => (
|
|
text.toLocaleString()));
|
|
this.setState({
|
|
files: {
|
|
...files,
|
|
share: keys,
|
|
},
|
|
});
|
|
onUploadShareFiles(keys).then().catch();
|
|
}}
|
|
treeData={getTreeNodes(treeData)}
|
|
/>
|
|
) : (
|
|
<div className='cvat-empty-share-tree'>
|
|
<Empty />
|
|
<Paragraph className='cvat-text-color'>
|
|
Please, be sure you had
|
|
<Text strong>
|
|
<a href={SHARE_MOUNT_GUIDE_URL}> mounted </a>
|
|
</Text>
|
|
share before you built CVAT and the shared storage contains files
|
|
</Paragraph>
|
|
</div>
|
|
)}
|
|
</Tabs.TabPane>
|
|
);
|
|
}
|
|
|
|
private renderRemoteSelector(): JSX.Element {
|
|
const { onUploadRemoteFiles } = this.props;
|
|
const { files } = this.state;
|
|
|
|
return (
|
|
<Tabs.TabPane key='remote' tab='Remote sources'>
|
|
<Input.TextArea
|
|
className='cvat-file-selector-remote'
|
|
placeholder='Enter one URL per line'
|
|
rows={6}
|
|
value={[...files.remote].join('\n')}
|
|
onChange={(event: React.ChangeEvent<HTMLTextAreaElement>): void => {
|
|
const urls = event.target.value.split('\n');
|
|
this.setState({
|
|
files: {
|
|
...files,
|
|
remote: urls,
|
|
},
|
|
});
|
|
onUploadRemoteFiles(urls.filter(Boolean));
|
|
}}
|
|
/>
|
|
</Tabs.TabPane>
|
|
);
|
|
}
|
|
|
|
private renderCloudStorageSelector(): JSX.Element {
|
|
const { cloudStorage, potentialCloudStorage, files } = this.state;
|
|
return (
|
|
<Tabs.TabPane
|
|
key='cloudStorage'
|
|
className='cvat-create-task-page-cloud-storage-tab'
|
|
tab={<span> Cloud Storage </span>}
|
|
>
|
|
<CloudStorageTab
|
|
formRef={this.cloudStorageTabFormRef}
|
|
cloudStorage={cloudStorage}
|
|
selectedFiles={files.cloudStorage.filter((item) => !item.endsWith('.jsonl'))}
|
|
onSelectCloudStorage={(_cloudStorage: CloudStorage | null) => {
|
|
this.setState({ cloudStorage: _cloudStorage });
|
|
}}
|
|
searchPhrase={potentialCloudStorage}
|
|
setSearchPhrase={(_potentialCloudStorage: string) => {
|
|
this.setState({ potentialCloudStorage: _potentialCloudStorage });
|
|
}}
|
|
onSelectFiles={this.handleUploadCloudStorageFiles}
|
|
/>
|
|
</Tabs.TabPane>
|
|
);
|
|
}
|
|
|
|
public render(): JSX.Element {
|
|
const { onChangeActiveKey, many } = this.props;
|
|
const { active } = this.state;
|
|
|
|
return (
|
|
<>
|
|
<Tabs
|
|
type='card'
|
|
activeKey={active}
|
|
tabBarGutter={5}
|
|
onChange={(activeKey: string): void => {
|
|
onChangeActiveKey(activeKey);
|
|
this.setState({
|
|
active: activeKey as any,
|
|
});
|
|
}}
|
|
>
|
|
{this.renderLocalSelector()}
|
|
{this.renderShareSelector()}
|
|
{this.renderRemoteSelector()}
|
|
{!many && this.renderCloudStorageSelector()}
|
|
</Tabs>
|
|
</>
|
|
);
|
|
}
|
|
}
|
|
|
|
export default FileManager;
|