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.
373 lines
14 KiB
TypeScript
373 lines
14 KiB
TypeScript
// Copyright (C) 2019-2021 Intel Corporation
|
|
//
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
import React from 'react';
|
|
import { Row, Col } from 'antd/lib/grid';
|
|
import Tag from 'antd/lib/tag';
|
|
import { CheckCircleOutlined, LoadingOutlined, WarningOutlined } from '@ant-design/icons';
|
|
import Modal from 'antd/lib/modal';
|
|
import notification from 'antd/lib/notification';
|
|
import Text from 'antd/lib/typography/Text';
|
|
import Title from 'antd/lib/typography/Title';
|
|
import moment from 'moment';
|
|
|
|
import getCore from 'cvat-core-wrapper';
|
|
import { getReposData, syncRepos } from 'utils/git-utils';
|
|
import { ActiveInference } from 'reducers/interfaces';
|
|
import AutomaticAnnotationProgress from 'components/tasks-page/automatic-annotation-progress';
|
|
import Descriptions from 'antd/lib/descriptions';
|
|
import UserSelector, { User } from './user-selector';
|
|
import BugTrackerEditor from './bug-tracker-editor';
|
|
import LabelsEditorComponent from '../labels-editor/labels-editor';
|
|
import ProjectSubsetField from '../create-task-page/project-subset-field';
|
|
|
|
const core = getCore();
|
|
|
|
interface Props {
|
|
previewImage: string;
|
|
taskInstance: any;
|
|
installedGit: boolean; // change to git repos url
|
|
activeInference: ActiveInference | null;
|
|
projectSubsets: string[];
|
|
cancelAutoAnnotation(): void;
|
|
onTaskUpdate: (taskInstance: any) => void;
|
|
}
|
|
|
|
interface State {
|
|
name: string;
|
|
subset: string;
|
|
repository: string;
|
|
repositoryStatus: string;
|
|
}
|
|
|
|
export default class DetailsComponent extends React.PureComponent<Props, State> {
|
|
private mounted: boolean;
|
|
|
|
private previewImageElement: HTMLImageElement;
|
|
|
|
private previewWrapperRef: React.RefObject<HTMLDivElement>;
|
|
|
|
constructor(props: Props) {
|
|
super(props);
|
|
|
|
const { taskInstance } = props;
|
|
|
|
this.mounted = false;
|
|
this.previewImageElement = new Image();
|
|
this.previewWrapperRef = React.createRef<HTMLDivElement>();
|
|
this.state = {
|
|
name: taskInstance.name,
|
|
subset: taskInstance.subset,
|
|
repository: '',
|
|
repositoryStatus: '',
|
|
};
|
|
}
|
|
|
|
public componentDidMount(): void {
|
|
const { taskInstance, previewImage } = this.props;
|
|
const { previewImageElement, previewWrapperRef } = this;
|
|
this.mounted = true;
|
|
|
|
previewImageElement.onload = () => {
|
|
const { height, width } = previewImageElement;
|
|
if (width > height) {
|
|
previewImageElement.style.width = '100%';
|
|
} else {
|
|
previewImageElement.style.height = '100%';
|
|
}
|
|
};
|
|
|
|
previewImageElement.src = previewImage;
|
|
previewImageElement.alt = 'Preview';
|
|
if (previewWrapperRef.current) {
|
|
previewWrapperRef.current.appendChild(previewImageElement);
|
|
}
|
|
|
|
getReposData(taskInstance.id)
|
|
.then((data): void => {
|
|
if (data !== null && this.mounted) {
|
|
if (data.status.error) {
|
|
notification.error({
|
|
message: 'Could not receive repository status',
|
|
description: data.status.error,
|
|
});
|
|
} else {
|
|
this.setState({
|
|
repositoryStatus: data.status.value,
|
|
});
|
|
}
|
|
|
|
this.setState({
|
|
repository: data.url,
|
|
});
|
|
}
|
|
})
|
|
.catch((error): void => {
|
|
if (this.mounted) {
|
|
notification.error({
|
|
message: 'Could not receive repository status',
|
|
description: error.toString(),
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
public componentDidUpdate(prevProps: Props): void {
|
|
const { taskInstance } = this.props;
|
|
|
|
if (prevProps !== this.props) {
|
|
this.setState({
|
|
name: taskInstance.name,
|
|
});
|
|
}
|
|
}
|
|
|
|
public componentWillUnmount(): void {
|
|
this.mounted = false;
|
|
}
|
|
|
|
private renderTaskName(): JSX.Element {
|
|
const { name } = this.state;
|
|
const { taskInstance, onTaskUpdate } = this.props;
|
|
|
|
return (
|
|
<Title
|
|
level={4}
|
|
editable={{
|
|
onChange: (value: string): void => {
|
|
this.setState({
|
|
name: value,
|
|
});
|
|
|
|
taskInstance.name = value;
|
|
onTaskUpdate(taskInstance);
|
|
},
|
|
}}
|
|
className='cvat-text-color'
|
|
>
|
|
{name}
|
|
</Title>
|
|
);
|
|
}
|
|
|
|
private renderPreview(): JSX.Element {
|
|
const { previewWrapperRef } = this;
|
|
|
|
// Add image on mount after get its width and height to fit it into wrapper
|
|
return <div ref={previewWrapperRef} className='cvat-task-preview-wrapper' />;
|
|
}
|
|
|
|
private renderParameters(): JSX.Element {
|
|
const { taskInstance } = this.props;
|
|
const { overlap, segmentSize, imageQuality } = taskInstance;
|
|
|
|
return (
|
|
<Descriptions className='cvat-task-parameters' bordered layout='vertical' size='small'>
|
|
<Descriptions.Item label='Overlap size'>{overlap}</Descriptions.Item>
|
|
<Descriptions.Item label='Segment size'>{segmentSize}</Descriptions.Item>
|
|
<Descriptions.Item label='Image quality'>{imageQuality}</Descriptions.Item>
|
|
</Descriptions>
|
|
);
|
|
}
|
|
|
|
private renderDescription(): JSX.Element {
|
|
const { taskInstance, onTaskUpdate } = this.props;
|
|
const owner = taskInstance.owner ? taskInstance.owner.username : null;
|
|
const assignee = taskInstance.assignee ? taskInstance.assignee : null;
|
|
const created = moment(taskInstance.createdDate).format('MMMM Do YYYY');
|
|
const assigneeSelect = (
|
|
<UserSelector
|
|
value={assignee}
|
|
onSelect={(value: User | null): void => {
|
|
taskInstance.assignee = value;
|
|
onTaskUpdate(taskInstance);
|
|
}}
|
|
/>
|
|
);
|
|
|
|
return (
|
|
<Row className='cvat-task-details-user-block' justify='space-between' align='middle'>
|
|
<Col span={12}>
|
|
{owner && (
|
|
<Text type='secondary'>{`Task #${taskInstance.id} Created by ${owner} on ${created}`}</Text>
|
|
)}
|
|
</Col>
|
|
<Col span={10}>
|
|
<Text type='secondary'>Assigned to</Text>
|
|
{assigneeSelect}
|
|
</Col>
|
|
</Row>
|
|
);
|
|
}
|
|
|
|
private renderDatasetRepository(): JSX.Element | boolean {
|
|
const { taskInstance } = this.props;
|
|
const { repository, repositoryStatus } = this.state;
|
|
|
|
return (
|
|
!!repository && (
|
|
<Row>
|
|
<Col className='cvat-dataset-repository-url'>
|
|
<Text strong className='cvat-text-color'>
|
|
Dataset Repository
|
|
</Text>
|
|
<br />
|
|
<a href={repository} rel='noopener noreferrer' target='_blank'>
|
|
{repository}
|
|
</a>
|
|
{repositoryStatus === 'sync' && (
|
|
<Tag color='blue'>
|
|
<CheckCircleOutlined />
|
|
Synchronized
|
|
</Tag>
|
|
)}
|
|
{repositoryStatus === 'merged' && (
|
|
<Tag color='green'>
|
|
<CheckCircleOutlined />
|
|
Merged
|
|
</Tag>
|
|
)}
|
|
{repositoryStatus === 'syncing' && (
|
|
<Tag color='purple'>
|
|
<LoadingOutlined />
|
|
Syncing
|
|
</Tag>
|
|
)}
|
|
{repositoryStatus === '!sync' && (
|
|
<Tag
|
|
color='red'
|
|
onClick={(): void => {
|
|
this.setState({
|
|
repositoryStatus: 'syncing',
|
|
});
|
|
|
|
syncRepos(taskInstance.id)
|
|
.then((): void => {
|
|
if (this.mounted) {
|
|
this.setState({
|
|
repositoryStatus: 'sync',
|
|
});
|
|
}
|
|
})
|
|
.catch((error): void => {
|
|
if (this.mounted) {
|
|
Modal.error({
|
|
width: 800,
|
|
title: 'Could not synchronize the repository',
|
|
content: error.toString(),
|
|
});
|
|
|
|
this.setState({
|
|
repositoryStatus: '!sync',
|
|
});
|
|
}
|
|
});
|
|
}}
|
|
>
|
|
<WarningOutlined />
|
|
Synchronize
|
|
</Tag>
|
|
)}
|
|
</Col>
|
|
</Row>
|
|
)
|
|
);
|
|
}
|
|
|
|
private renderLabelsEditor(): JSX.Element {
|
|
const { taskInstance, onTaskUpdate } = this.props;
|
|
|
|
return (
|
|
<Row>
|
|
<Col span={24}>
|
|
<LabelsEditorComponent
|
|
labels={taskInstance.labels.map((label: any): string => label.toJSON())}
|
|
onSubmit={(labels: any[]): void => {
|
|
taskInstance.labels = labels.map((labelData): any => new core.classes.Label(labelData));
|
|
onTaskUpdate(taskInstance);
|
|
}}
|
|
/>
|
|
</Col>
|
|
</Row>
|
|
);
|
|
}
|
|
|
|
private renderSubsetField(): JSX.Element {
|
|
const { subset } = this.state;
|
|
const { taskInstance, projectSubsets, onTaskUpdate } = this.props;
|
|
|
|
return (
|
|
<Row>
|
|
<Col span={24}>
|
|
<Text className='cvat-text-color'>Subset:</Text>
|
|
</Col>
|
|
<Col span={24}>
|
|
<ProjectSubsetField
|
|
value={subset}
|
|
projectId={taskInstance.projectId}
|
|
projectSubsets={projectSubsets}
|
|
onChange={(value) => {
|
|
this.setState({
|
|
subset: value,
|
|
});
|
|
|
|
if (taskInstance.subset !== value) {
|
|
taskInstance.subset = value;
|
|
onTaskUpdate(taskInstance);
|
|
}
|
|
}}
|
|
/>
|
|
</Col>
|
|
</Row>
|
|
);
|
|
}
|
|
|
|
public render(): JSX.Element {
|
|
const {
|
|
activeInference, cancelAutoAnnotation, taskInstance, onTaskUpdate,
|
|
} = this.props;
|
|
|
|
return (
|
|
<div className='cvat-task-details'>
|
|
<Row justify='start' align='middle'>
|
|
<Col className='cvat-task-details-task-name'>{this.renderTaskName()}</Col>
|
|
</Row>
|
|
<Row justify='space-between' align='top'>
|
|
<Col md={8} lg={7} xl={7} xxl={6}>
|
|
<Row justify='start' align='middle'>
|
|
<Col span={24}>{this.renderPreview()}</Col>
|
|
</Row>
|
|
<Row>
|
|
<Col span={24}>{this.renderParameters()}</Col>
|
|
</Row>
|
|
</Col>
|
|
<Col md={16} lg={17} xl={17} xxl={18}>
|
|
{this.renderDescription()}
|
|
<Row justify='space-between' align='middle'>
|
|
<Col span={12}>
|
|
<BugTrackerEditor
|
|
instance={taskInstance}
|
|
onChange={(bugTracker) => {
|
|
taskInstance.bugTracker = bugTracker;
|
|
onTaskUpdate(taskInstance);
|
|
}}
|
|
/>
|
|
</Col>
|
|
<Col span={10}>
|
|
<AutomaticAnnotationProgress
|
|
activeInference={activeInference}
|
|
cancelAutoAnnotation={cancelAutoAnnotation}
|
|
/>
|
|
</Col>
|
|
</Row>
|
|
{this.renderDatasetRepository()}
|
|
{!taskInstance.projectId && this.renderLabelsEditor()}
|
|
{taskInstance.projectId && this.renderSubsetField()}
|
|
</Col>
|
|
</Row>
|
|
</div>
|
|
);
|
|
}
|
|
}
|