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.
243 lines
8.2 KiB
TypeScript
243 lines
8.2 KiB
TypeScript
// Copyright (C) 2020 Intel Corporation
|
|
//
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
import './styles.scss';
|
|
import React 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 Upload, { RcFile } from 'antd/lib/upload';
|
|
import Empty from 'antd/lib/empty';
|
|
import Tree, { AntTreeNode, TreeNodeNormal } from 'antd/lib/tree/Tree';
|
|
import { InboxOutlined } from '@ant-design/icons';
|
|
|
|
import consts from 'consts';
|
|
|
|
export interface Files {
|
|
local: File[];
|
|
share: string[];
|
|
remote: string[];
|
|
}
|
|
|
|
interface State {
|
|
files: Files;
|
|
expandedKeys: string[];
|
|
active: 'local' | 'share' | 'remote';
|
|
}
|
|
|
|
interface Props {
|
|
withRemote: boolean;
|
|
treeData: TreeNodeNormal[];
|
|
onLoadData: (key: string, success: () => void, failure: () => void) => void;
|
|
onChangeActiveKey(key: string): void;
|
|
}
|
|
|
|
export default class FileManager extends React.PureComponent<Props, State> {
|
|
public constructor(props: Props) {
|
|
super(props);
|
|
|
|
this.state = {
|
|
files: {
|
|
local: [],
|
|
share: [],
|
|
remote: [],
|
|
},
|
|
expandedKeys: [],
|
|
active: 'local',
|
|
};
|
|
|
|
this.loadData('/');
|
|
}
|
|
|
|
public getFiles(): Files {
|
|
const { active, files } = this.state;
|
|
return {
|
|
local: active === 'local' ? files.local : [],
|
|
share: active === 'share' ? files.share : [],
|
|
remote: active === 'remote' ? files.remote : [],
|
|
};
|
|
}
|
|
|
|
private loadData = (key: string): Promise<void> =>
|
|
new Promise<void>((resolve, reject): void => {
|
|
const { onLoadData } = this.props;
|
|
|
|
const success = (): void => resolve();
|
|
const failure = (): void => reject();
|
|
onLoadData(key, success, failure);
|
|
});
|
|
|
|
public reset(): void {
|
|
this.setState({
|
|
expandedKeys: [],
|
|
active: 'local',
|
|
files: {
|
|
local: [],
|
|
share: [],
|
|
remote: [],
|
|
},
|
|
});
|
|
}
|
|
|
|
private renderLocalSelector(): JSX.Element {
|
|
const { files } = this.state;
|
|
|
|
return (
|
|
<Tabs.TabPane key='local' tab='My computer'>
|
|
<Upload.Dragger
|
|
multiple
|
|
listType='text'
|
|
fileList={files.local as any[]}
|
|
showUploadList={
|
|
files.local.length < 5 && {
|
|
showRemoveIcon: false,
|
|
}
|
|
}
|
|
beforeUpload={(_: RcFile, newLocalFiles: RcFile[]): boolean => {
|
|
this.setState({
|
|
files: {
|
|
...files,
|
|
local: newLocalFiles,
|
|
},
|
|
});
|
|
return false;
|
|
}}
|
|
>
|
|
<p className='ant-upload-drag-icon'>
|
|
<InboxOutlined />
|
|
</p>
|
|
<p className='ant-upload-text'>Click or drag files to this area</p>
|
|
<p className='ant-upload-hint'>Support for a bulk images or a single video</p>
|
|
</Upload.Dragger>
|
|
{files.local.length >= 5 && (
|
|
<>
|
|
<br />
|
|
<Text className='cvat-text-color'>{`${files.local.length} files selected`}</Text>
|
|
</>
|
|
)}
|
|
</Tabs.TabPane>
|
|
);
|
|
}
|
|
|
|
private renderShareSelector(): JSX.Element {
|
|
function renderTreeNodes(data: TreeNodeNormal[]): JSX.Element[] {
|
|
// sort alphabetically
|
|
data.sort((a: TreeNodeNormal, b: TreeNodeNormal): number => a.key.localeCompare(b.key));
|
|
return data.map((item: TreeNodeNormal) => {
|
|
if (item.children) {
|
|
return (
|
|
<Tree.TreeNode title={item.title} key={item.key} dataRef={item} isLeaf={item.isLeaf}>
|
|
{renderTreeNodes(item.children)}
|
|
</Tree.TreeNode>
|
|
);
|
|
}
|
|
|
|
return <Tree.TreeNode key={item.key} {...item} dataRef={item} />;
|
|
});
|
|
}
|
|
|
|
const { SHARE_MOUNT_GUIDE_URL } = consts;
|
|
const { treeData } = 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
|
|
checkStrictly={false}
|
|
expandedKeys={expandedKeys}
|
|
checkedKeys={files.share}
|
|
loadData={(node: AntTreeNode): Promise<void> => this.loadData(node.props.dataRef.key)}
|
|
onExpand={(newExpandedKeys: string[]): void => {
|
|
this.setState({
|
|
expandedKeys: newExpandedKeys,
|
|
});
|
|
}}
|
|
onCheck={(
|
|
checkedKeys:
|
|
| string[]
|
|
| {
|
|
checked: string[];
|
|
halfChecked: string[];
|
|
},
|
|
): void => {
|
|
const keys = checkedKeys as string[];
|
|
this.setState({
|
|
files: {
|
|
...files,
|
|
share: keys,
|
|
},
|
|
});
|
|
}}
|
|
>
|
|
{renderTreeNodes(treeData)}
|
|
</Tree>
|
|
) : (
|
|
<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 { files } = this.state;
|
|
|
|
return (
|
|
<Tabs.TabPane key='remote' tab='Remote sources'>
|
|
<Input.TextArea
|
|
placeholder='Enter one URL per line'
|
|
rows={6}
|
|
value={[...files.remote].join('\n')}
|
|
onChange={(event: React.ChangeEvent<HTMLTextAreaElement>): void => {
|
|
this.setState({
|
|
files: {
|
|
...files,
|
|
remote: event.target.value.split('\n'),
|
|
},
|
|
});
|
|
}}
|
|
/>
|
|
</Tabs.TabPane>
|
|
);
|
|
}
|
|
|
|
public render(): JSX.Element {
|
|
const { withRemote, onChangeActiveKey } = 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()}
|
|
{withRemote && this.renderRemoteSelector()}
|
|
</Tabs>
|
|
</>
|
|
);
|
|
}
|
|
}
|